[PATCH] dump and restore domain trust info
Philipp Gesang
philipp.gesang at intra2net.com
Thu Jan 10 10:53:52 UTC 2019
Hello and a late happy New Year everyone!
While integrating Samba with our backup system, I’ve been adding functionality
for dumping and undumping the domain member information in a hopefully portable
way. I think I have now reached a point where I’d like to elicit external
feedback so I would like you have a look at the attached patchset. Eventually
we would like for this functionality to be merged.
After some experiments I settled on extending “net primarytrust dumpinfo” with
json output and adding a companion “net primarytrust readinfo” for replaying a
dump obtained this way.
An example dump as used in the blackbox tests:
{ "Reserved Flags": "AAAAAAAAAAA=",
"Join Time": "KgAAAAAAAAA=",
"Computer Name": "LOCALADMEMBER",
"Account Name": "LOCALADMEMBER$",
"Secure Channel Type": 2,
"Trust Flags": 26,
"Trust Type": 2,
"Trust Attributes": 26,
"Supported Encryption Types": 31,
"Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
"Password Last Change": "NWUTXAAAAAA=",
"Password Changes": "AQAAAAAAAAA=",
"Password": {
"Change Time": "ysIkXAAAAAA=",
"Change Server": "ADDC",
"Cleartext Blob": "Erzx4o2+ZLrW+kx/dHn+s8Al9i6IYHp5mOLfa7Vi5qB/bZ3hSTyRcSxsguu3A5gE+GAP6mh7cOzDo7njgPUYdzB2qnbi5sVsMznTb3Zgz6ts8R5p+2+W97b2bL4sf445/D/rOkU5pLMAcyG+HbyH9wQ81ng8Ye13nuD+5+i6vXmivG3zqij4veVo6aeob0H6fOOUqzjpzOmHt0w3k3Nl/Efo3KrNsrAtUDpQ+sKxvPNOdqdzCzxWc1esAS8VYxI/T3jPLc11rWcr7y4uJPP0+Dali6XWrnnrZvw3LF25njI2N/7kNPiMK1gner8WaitimG5hMXKu86xWdOYB1rawshF6+Wf2rYNj7bVzNNG2QG2/L/2iLu5N4JqjDSw++39wujr+eR/2S7T/AEpuBjQ=" },
"DNS Domain Info": {
"Domain NetBios Name": "ADDOMAIN",
"Domain DNS Name": "addom.samba.example.com",
"Domain Forest Name": "addom.samba.example.com",
"Domain SID": "S-1-5-21-42-1337-1701",
"Domain GUID": "ec0ef791-e41e-44b7-8990-f05eacb06174" } }
Two patches contain the meat of it:
s3: net: add json printer to `net primarytrust`
s3: net: add primarytrust subcommand `readinfo`
There’s one patch that fixes some typos, the rest is auxiliary stuff and tests.
I’ve marked some issues with XXX comments. These mainly concern how flags
values should be represented.
CI: https://gitlab.com/samba-team/devel/samba/pipelines/42583194
I’m sorting out that failure in build_samba right now.
PS: FWIW, “readinfo” can be used to inject “offline join” blobs generated by
djoin.exe. If you’re interested I have a PoC that I can share.
Feedback and directions appreciated,
Philipp
-------------- next part --------------
From 853aaf37eb241fd7123b4b827d36608e6aa54433 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 8 Oct 2018 14:59:50 +0200
Subject: [RFC PATCH 01/10] lib/audit_logging/test: fix typos
Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
lib/audit_logging/audit_logging.c | 4 +-
.../tests/audit_logging_error_test.c | 50 +++++++++----------
2 files changed, 27 insertions(+), 27 deletions(-)
diff --git a/lib/audit_logging/audit_logging.c b/lib/audit_logging/audit_logging.c
index 6944da7f872..fe2df2c9f8a 100644
--- a/lib/audit_logging/audit_logging.c
+++ b/lib/audit_logging/audit_logging.c
@@ -293,7 +293,7 @@ void audit_message_send(
* Create a new json object, the json_object wraps the underlying json
* implementations JSON Object representation.
*
- * Free with a call to json_free_object, note that the jansson inplementation
+ * Free with a call to json_free_object, note that the jansson implementation
* allocates memory with malloc and not talloc.
*
* @return a struct json_object, valid will be set to false if the object
@@ -320,7 +320,7 @@ struct json_object json_new_object(void) {
* Create a new json object, the json_object wraps the underlying json
* implementations JSON Array representation.
*
- * Free with a call to json_free_object, note that the jansson inplementation
+ * Free with a call to json_free_object, note that the jansson implementation
* allocates memory with malloc and not talloc.
*
* @return a struct json_object, error will be set to true if the array
diff --git a/lib/audit_logging/tests/audit_logging_error_test.c b/lib/audit_logging/tests/audit_logging_error_test.c
index 1c0929a1b99..153e4f5b1fa 100644
--- a/lib/audit_logging/tests/audit_logging_error_test.c
+++ b/lib/audit_logging/tests/audit_logging_error_test.c
@@ -55,7 +55,7 @@
#include "lib/audit_logging/audit_logging.h"
-const int JANNASON_FAILURE = -1;
+const int JANSSON_FAILURE = -1;
const int CALL_ORIG = -2;
/*
@@ -300,7 +300,7 @@ static void test_json_add_int(void **state)
* Test json object set new failure
*/
will_return(__wrap_json_integer, false);
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_int(&object, "name", 2);
assert_false(json_is_invalid(&object));
@@ -320,7 +320,7 @@ static void test_json_add_bool(void **state)
* json_boolean does not return an error code.
* Test json object set new failure
*/
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_bool(&object, "name", true);
assert_false(json_is_invalid(&object));
@@ -351,7 +351,7 @@ static void test_json_add_string(void **state)
* Test json object set new failure
*/
will_return(__wrap_json_string, false);
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_string(&object, "name", "value");
assert_false(json_is_invalid(&object));
@@ -360,7 +360,7 @@ static void test_json_add_string(void **state)
/*
* Test json object set new failure for a NULL string
*/
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_string(&object, "null", NULL);
assert_false(json_is_invalid(&object));
@@ -384,7 +384,7 @@ static void test_json_add_object(void **state)
/*
* Test json object set new failure
*/
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_object(&object, "name", &value);
assert_false(json_is_invalid(&object));
@@ -394,7 +394,7 @@ static void test_json_add_object(void **state)
/*
* Test json object set new failure for a NULL value
*/
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_object(&object, "null", NULL);
assert_false(json_is_invalid(&object));
@@ -419,7 +419,7 @@ static void test_json_add_to_array(void **state)
/*
* Test json array append new failure
*/
- will_return(__wrap_json_array_append_new, JANNASON_FAILURE);
+ will_return(__wrap_json_array_append_new, JANSSON_FAILURE);
rc = json_add_object(&array, "name", &value);
assert_false(json_is_invalid(&array));
@@ -429,7 +429,7 @@ static void test_json_add_to_array(void **state)
/*
* Test json append new failure with a NULL value
*/
- will_return(__wrap_json_array_append_new, JANNASON_FAILURE);
+ will_return(__wrap_json_array_append_new, JANSSON_FAILURE);
rc = json_add_object(&array, "null", NULL);
assert_false(json_is_invalid(&array));
@@ -461,7 +461,7 @@ static void test_json_add_timestamp(void **state)
will_return(__wrap_gettimeofday, 0);
will_return(__wrap_localtime, false);
will_return(__wrap_json_string, false);
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_timestamp(&object);
assert_false(json_is_invalid(&object));
@@ -511,7 +511,7 @@ static void test_json_add_stringn(void **state)
* Test json object set new failure
*/
will_return(__wrap_json_string, false);
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_stringn(&object, "name", "value", 3);
assert_false(json_is_invalid(&object));
@@ -520,7 +520,7 @@ static void test_json_add_stringn(void **state)
/*
* Test json object set new failure for a NULL string
*/
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_stringn(&object, "null", NULL, 2);
assert_false(json_is_invalid(&object));
@@ -529,7 +529,7 @@ static void test_json_add_stringn(void **state)
/*
* Test json object set new failure for a zero string size
*/
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_stringn(&object, "zero", "no value", 0);
assert_false(json_is_invalid(&object));
@@ -567,7 +567,7 @@ static void test_json_add_version(void **state)
will_return(__wrap_json_object, false);
will_return(__wrap_json_integer, false);
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_version(&object, 2, 12);
assert_false(json_is_invalid(&object));
@@ -588,7 +588,7 @@ static void test_json_add_version(void **state)
will_return(__wrap_json_integer, false);
will_return(__wrap_json_object_set_new, CALL_ORIG);
will_return(__wrap_json_integer, false);
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_version(&object, 3, 13);
assert_false(json_is_invalid(&object));
@@ -609,7 +609,7 @@ static void test_json_add_version(void **state)
will_return(__wrap_json_integer, false);
will_return(__wrap_json_object_set_new, CALL_ORIG);
will_return(__wrap_json_integer, false);
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_version(&object, 4, 14);
assert_false(json_is_invalid(&object));
@@ -635,7 +635,7 @@ static void test_json_add_address(void **state)
will_return(__wrap_json_object, false);
object = json_new_object();
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_address(&object, "name", NULL);
assert_false(json_is_invalid(&object));
@@ -650,7 +650,7 @@ static void test_json_add_address(void **state)
will_return(__wrap_talloc_named_const, REAL_TALLOC);
will_return(__wrap_tsocket_address_string, false);
will_return(__wrap_json_string, false);
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_address(&object, "name", ip);
assert_false(json_is_invalid(&object));
@@ -698,7 +698,7 @@ static void test_json_add_sid(void **state)
will_return(__wrap_json_object, false);
object = json_new_object();
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_sid(&object, "null", NULL);
assert_int_equal(JSON_ERROR, rc);
@@ -707,7 +707,7 @@ static void test_json_add_sid(void **state)
*/
assert_true(string_to_sid(&sid, SID));
will_return(__wrap_json_string, false);
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_sid(&object, "sid", &sid);
assert_int_equal(JSON_ERROR, rc);
@@ -728,7 +728,7 @@ static void test_json_add_guid(void **state)
will_return(__wrap_json_object, false);
object = json_new_object();
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_guid(&object, "null", NULL);
assert_int_equal(JSON_ERROR, rc);
@@ -738,7 +738,7 @@ static void test_json_add_guid(void **state)
status = GUID_from_string(GUID, &guid);
assert_true(NT_STATUS_IS_OK(status));
will_return(__wrap_json_string, false);
- will_return(__wrap_json_object_set_new, JANNASON_FAILURE);
+ will_return(__wrap_json_object_set_new, JANSSON_FAILURE);
rc = json_add_guid(&object, "guid", &guid);
assert_int_equal(JSON_ERROR, rc);
@@ -818,7 +818,7 @@ static void test_json_get_object(void **state)
{
struct json_object object;
struct json_object stored;
- struct json_object retreived;
+ struct json_object retrieved;
int rc;
@@ -839,8 +839,8 @@ static void test_json_get_object(void **state)
*/
will_return(__wrap_json_object, false);
will_return(__wrap_json_object_update, true);
- retreived = json_get_object(&object, "stored");
- assert_true(json_is_invalid(&retreived));
+ retrieved = json_get_object(&object, "stored");
+ assert_true(json_is_invalid(&retrieved));
json_free(&object);
}
--
2.17.2
From 74cf2bc676264a0a716489b74bd7c120d9975789 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 1 Oct 2018 11:24:56 +0200
Subject: [RFC PATCH 02/10] lib/audit_logging: add JSON object accessors
This adds a wrappers for libjansson routines to handle a greater
variety of data types:
- json_has_key(),
- json_add_uint32(),
- json_add_uint64(),
- json_add_base64(),
- json_get_int(),
- json_get_string(),
- json_get_sid(),
- json_get_uint64_t(),
- json_get_uint32_t(),
- json_get_guid(), and
- json_get_base64().
Also add helpers ``json_from_{string,FILEp}()`` for decoding JSON
from strings and file handles, respectively. The inputs may be
either JSON objects or arrays. Add a new constructor
``json_new_null()`` to make it more obvious that incoming JSON
values may be of any type.
Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
lib/audit_logging/audit_logging.c | 880 +++++++++++++++++++++++++++++-
lib/audit_logging/audit_logging.h | 43 ++
2 files changed, 920 insertions(+), 3 deletions(-)
diff --git a/lib/audit_logging/audit_logging.c b/lib/audit_logging/audit_logging.c
index fe2df2c9f8a..41c2ce9b901 100644
--- a/lib/audit_logging/audit_logging.c
+++ b/lib/audit_logging/audit_logging.c
@@ -24,10 +24,13 @@
#include "includes.h"
+#include "inttypes.h"
#include "librpc/ndr/libndr.h"
#include "lib/tsocket/tsocket.h"
#include "libcli/security/dom_sid.h"
#include "lib/messaging/messaging.h"
+#include "lib/util/base64.h"
+#include "lib/util/byteorder.h"
#include "auth/common_auth.h"
#include "audit_logging.h"
@@ -84,7 +87,7 @@ char* audit_get_timestamp(TALLOC_CTX *frame)
*
* @param prefix Text to be printed at the start of the log line
* @param message The content of the log line.
- * @param debub_class The debug class to log the message with.
+ * @param debug_class The debug class to log the message with.
* @param debug_level The debug level to log the message with.
*/
void audit_log_human_text(const char* prefix,
@@ -96,17 +99,64 @@ void audit_log_human_text(const char* prefix,
}
#ifdef HAVE_JANSSON
+
/*
* Constant for empty json object initialisation
*/
const struct json_object json_empty_object = {.valid = false, .root = NULL};
+
+/*
+ * @brief Get string representation of the type of a JSON entity.
+ *
+ * Returns the JSON type name, ``UNKNOWN'' otherwise. This is operates
+ * directly on libjansson types.
+ *
+ * @param jsobj The object whose type to return.
+ */
+static const char *json_type_to_string(const json_type jstype)
+{
+ switch (jstype) {
+ default: return "UNKNOWN";
+ case JSON_OBJECT: return "object";
+ case JSON_ARRAY: return "array";
+ case JSON_STRING: return "string";
+ case JSON_INTEGER: return "integer";
+ case JSON_REAL: return "real";
+ case JSON_TRUE: return "true";
+ case JSON_FALSE: return "false";
+ case JSON_NULL: return "null";
+ }
+}
+
+/*
+ * @brief Get string representation of the type of a json_object entity.
+ *
+ * Returns a human-readable type name for the toplevel entity in a json_object,
+ * ``INVALID'' for a bad object, and ``UNKNOWN'' in case the type is not
+ * present in our definitions.
+ *
+ * @param jsobj The object whose type to return.
+ */
+static const char *json_object_type_to_string(const struct json_object *jsobj)
+{
+ json_type jstype = JSON_NULL;
+
+ if (json_is_invalid(jsobj)) {
+ return "INVALID";
+ }
+
+ jstype = json_typeof(jsobj->root);
+
+ return json_type_to_string(jstype);
+}
+
/*
* @brief write a json object to the samba audit logs.
*
* Write the json object to the audit logs as a formatted string
*
* @param message The content of the log line.
- * @param debub_class The debug class to log the message with.
+ * @param debug_class The debug class to log the message with.
* @param debug_level The debug level to log the message with.
*/
void audit_log_json(struct json_object* message,
@@ -341,6 +391,31 @@ struct json_object json_new_array(void) {
return array;
}
+/*
+ * @brief Create a new struct json_object, wrapping a JSON value of
+ * NULL type.
+ *
+ * Create a new object of NULL type.
+ *
+ * Free with a call to json_free_object, note that the jansson implementation
+ * allocates memory with malloc and not talloc.
+ *
+ * @return a struct json_object, valid will be set to false if the object
+ * could not be created.
+ *
+ */
+struct json_object json_new_null(void) {
+ struct json_object object = json_empty_object;
+
+ object.root = json_null();
+ if (object.root == NULL) {
+ object.valid = false;
+ DBG_ERR("Unable to create JSON null value\n");
+ return object;
+ }
+ object.valid = true;
+ return object;
+}
/*
* @brief free and invalidate a previously created JSON object.
@@ -413,6 +488,294 @@ int json_add_int(struct json_object *object, const char *name, const int value)
return ret;
}
+/*
+ * @brief Extract integer from JSON object.
+ *
+ * Read a field from the object as a JSON number and return the result as
+ * integer.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param value where to store the result.
+ *
+ * @return 0 the operation was successful
+ * -1 the operation failed
+ *
+ */
+int json_get_int(const struct json_object *object, const char *name,
+ int *value)
+{
+ const json_t *jstmp = NULL;
+
+ if (object == NULL || json_is_invalid(object)) {
+ DBG_ERR("Unable to retrieve integer value [%s] "
+ "from invalid object\n",
+ name);
+ return JSON_ERROR;
+ }
+
+ if (name == NULL) {
+ DBG_ERR("Unable to retrieve integer value, specified "
+ "field is NULL\n");
+ return JSON_ERROR;
+ }
+
+ if (value == NULL) {
+ DBG_ERR("Unable to store integer value [%s]: "
+ "target location is NULL\n",
+ name);
+ return JSON_ERROR;
+ }
+
+ jstmp = json_object_get(object->root, name);
+ if (jstmp == NULL) {
+ DBG_ERR("JSON object has no key [%s]\n", name);
+ return JSON_ERROR;
+ }
+
+ if (!json_is_integer (jstmp)) {
+ DBG_ERR("value of key [%s] is not an integer but a JSON "
+ "entity of type %s\n",
+ name, json_type_to_string(json_typeof(jstmp)));
+ return JSON_ERROR;
+ }
+
+ *value = json_integer_value(jstmp);
+
+ return 0;
+}
+
+/*
+ * @brief Add an 64 bit unsigned integer value to a JSON object.
+ *
+ * Add an integer value named 'name' to the json object. JSON numbers
+ * are unsuitable for this purpose because they are represented as doubles.
+ * Thus we fall back on base64 encoded data in SMB byte order.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param value the value.
+ *
+ * @return 0 the operation was successful
+ * -1 the operation failed
+ *
+ */
+int json_add_uint64(struct json_object *object, const char *name,
+ const uint64_t value)
+{
+ uint8_t buf [sizeof (uint64_t)] = { 0 };
+
+ if (json_is_invalid(object)) {
+ DBG_ERR("Unable to add uint64_t [%s] value [%"PRIu64"], "
+ "target object is invalid\n",
+ name,
+ value);
+ return JSON_ERROR;
+ }
+
+ SBVAL(buf, 0, value);
+
+ return json_add_base64(object, name,
+ (DATA_BLOB)
+ { .data = buf
+ , .length = sizeof (buf) });
+}
+
+/*
+ * @brief Extract 64 bit unsigned integer from JSON object.
+ *
+ * Read a base64 encoded integer value named 'name' from the json object.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param value where to store the result.
+ *
+ * @return 0 the operation was successful
+ * -1 the operation failed
+ *
+ */
+int json_get_uint64(const struct json_object *object, const char *name,
+ uint64_t *value)
+{
+ int ret = 0;
+ DATA_BLOB rawu64 = { 0 };
+ TALLOC_CTX *ctx = NULL;
+
+ if (value == NULL) {
+ DBG_ERR("Unable to store uint64_t value [%s]: "
+ "target location is NULL\n",
+ name);
+ return JSON_ERROR;
+ }
+
+ ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ DBG_ERR("Out of memory creating temporary context\n");
+ return JSON_ERROR;
+ }
+
+ /* object and name are validated indirectly here */
+ ret = json_get_base64(ctx, object, name, &rawu64);
+ if (ret != 0) {
+ DBG_ERR("Unable to read field [%s] as base64 encoded data\n",
+ name);
+ TALLOC_FREE(ctx);
+ return JSON_ERROR;
+ }
+
+ if (rawu64.length != sizeof (uint64_t)) {
+ DBG_ERR("Invalid decoded base64 encoded field [%s]: content "
+ "length (%zu B) inadequate for uint64_t\n",
+ name, rawu64.length);
+ TALLOC_FREE(ctx);
+ return JSON_ERROR;
+ }
+
+ *value = BVAL(rawu64.data, 0);
+
+ data_blob_free(&rawu64);
+ TALLOC_FREE(ctx);
+
+ return 0;
+}
+
+/*
+ * @brief Add an 32 bit unsigned integer value to a JSON object.
+ *
+ * Add an integer value named 'name' to the json object. This can be
+ * represented by JSON numbers so we'll use those directly.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param value the value.
+ *
+ * @return 0 the operation was successful
+ * -1 the operation failed
+ *
+ */
+int json_add_uint32(struct json_object *object, const char *name,
+ const uint32_t value)
+{
+ int ret = 0;
+ json_t *integer = NULL;
+
+ if (json_is_invalid(object)) {
+ DBG_ERR("Unable to add uint32_t [%s] value [%"PRIu32"], "
+ "target object is invalid\n",
+ name,
+ value);
+ return JSON_ERROR;
+ }
+
+ integer = json_integer(value);
+ if (integer == NULL) {
+ DBG_ERR("Unable to create value [%s] for uint32_t [%"PRIu32"]\n",
+ name,
+ value);
+ return JSON_ERROR;
+ }
+
+ ret = json_object_set_new(object->root, name, integer);
+ if (ret != 0) {
+ json_decref(integer);
+ DBG_ERR("Unable to add uint32_t [%s] value [%"PRIu32"]\n",
+ name, value);
+ }
+
+ return ret;
+}
+
+/*
+ * @brief Extract 32 bit unsigned integer from JSON object.
+ *
+ * Read a field from the object as a JSON number and return the result as
+ * integer.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param value where to store the result.
+ *
+ * @return 0 the operation was successful
+ * -1 the operation failed
+ *
+ */
+int json_get_uint32(const struct json_object *object, const char *name,
+ uint32_t *value)
+{
+ const json_t *jstmp = NULL;
+
+ if (object == NULL || json_is_invalid(object)) {
+ DBG_ERR("Unable to retrieve uint32_t value [%s] "
+ "from invalid object\n",
+ name);
+ return JSON_ERROR;
+ }
+
+ if (name == NULL) {
+ DBG_ERR("Unable to retrieve uint32 value, specified "
+ "field is NULL\n");
+ return JSON_ERROR;
+ }
+
+ if (value == NULL) {
+ DBG_ERR("Unable to store uint32 value [%s]: "
+ "target location is NULL\n",
+ name);
+ return JSON_ERROR;
+ }
+
+ jstmp = json_object_get(object->root, name);
+ if (jstmp == NULL) {
+ DBG_ERR("JSON object has no key [%s]\n", name);
+ return JSON_ERROR;
+ }
+
+ if (!json_is_integer (jstmp)) {
+ DBG_ERR("value of key [%s] is not an integer but a JSON "
+ "entity of type %s\n",
+ name, json_type_to_string(json_typeof(jstmp)));
+ return JSON_ERROR;
+ }
+
+ *value = (uint32_t)json_integer_value(jstmp);
+
+ return 0;
+}
+
+/*
+ * @brief Add a unix timestamp to a JSON object.
+ *
+ * time_t is a typedef for long long on x64 bit systems so we store it as
+ * int64_t.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param t the value
+ *
+ * @return 0 the operation was successful
+ * -1 the operation failed
+ *
+ */
+int json_add_time_t(struct json_object *object, const char *name, const time_t t)
+{
+ uint8_t buf [sizeof (int64_t)] = { 0 };
+
+ if (json_is_invalid(object)) {
+ DBG_ERR("Unable to add time_t [%s] value [%ld], "
+ "target object is invalid\n",
+ name,
+ t);
+ return JSON_ERROR;
+ }
+
+ SBVAL(buf, 0, (int64_t)t);
+
+ return json_add_base64(object, name,
+ (DATA_BLOB)
+ { .data = buf
+ , .length = sizeof (buf) });
+}
+
/*
* @brief Add a boolean value to a JSON object.
*
@@ -495,6 +858,78 @@ int json_add_string(struct json_object *object,
return ret;
}
+/*
+ * @brief Get string value from a JSON object.
+ *
+ * Retrieve the string value with the key `name' from a json object
+ * and store it at `*value'. The destination pointer is only updated
+ * when all input checks succeed.
+ *
+ * @param object The JSON object to access.
+ * @param name Key.
+ * @param value Pointer to the destination string.
+ *
+ * @return 0 the operation was successful
+ * -1 the operation failed
+ */
+int json_get_string(TALLOC_CTX *ctx,
+ const struct json_object *object,
+ const char *name,
+ const char **value)
+{
+ const char *sttmp = NULL;
+ const json_t *jstmp = NULL;
+
+ if (object == NULL) {
+ DBG_ERR("Invalid argument, refusing to operate on NULL"
+ "object\n");
+ return JSON_ERROR;
+ }
+
+ if (name == NULL) {
+ DBG_ERR("Invalid argument, refusing to operate on NULL "
+ "as key\n");
+ return JSON_ERROR;
+ }
+
+ if (value == NULL) {
+ DBG_ERR("Invalid argument, refusing to operate on NULL "
+ "target\n");
+ return JSON_ERROR;
+ }
+
+ if (json_is_invalid(object)) {
+ DBG_ERR("Invalid argument, refusing to operate on invalid "
+ "object\n");
+ return JSON_ERROR;
+ }
+
+ if (!json_is_object(object->root)) {
+ DBG_ERR("refusing to operate on non-object JSON entity of "
+ "type %s\n",
+ json_object_type_to_string(object));
+ return JSON_ERROR;
+ }
+
+ jstmp = json_object_get(object->root, name);
+ if (jstmp == NULL) {
+ DBG_ERR("JSON object has no key [%s]\n", name);
+ return JSON_ERROR;
+ }
+
+ sttmp = talloc_strdup(ctx, json_string_value(jstmp));
+ if (sttmp == NULL) {
+ DBG_ERR("value of key [%s] is not a string but a JSON "
+ "entity of type %s\n",
+ name, json_type_to_string(json_typeof(jstmp)));
+ return JSON_ERROR;
+ }
+
+ *value = sttmp;
+
+ return 0;
+}
+
/*
* @brief Assert that the current JSON object is an array.
*
@@ -851,6 +1286,82 @@ int json_add_sid(struct json_object *object,
return ret;
}
+/*
+ * @brief Add data as Base64 encoded string.
+ *
+ * Wrapper for json_add_string() which converts the passed byte array to base64
+ * first.
+ *
+ * "foo":"YmFyCg=="
+ *
+ * @param object the JSON object to be updated.
+ * @param name the key
+ * @param data pointer to the start of the data to be encoded
+ * @param len number of bytes to process
+ *
+ * @return 0 the operation was successful
+ * -1 the operation failed
+ */
+int json_add_base64(struct json_object *object,
+ const char *name,
+ const DATA_BLOB src)
+{
+ int ret = 0;
+
+ if (json_is_invalid(object)) {
+ DBG_ERR("Unable to add base64 data [%s], "
+ "target object is invalid\n",
+ name);
+ return JSON_ERROR;
+ }
+
+ if (src.data == NULL) {
+ ret = json_object_set_new(object->root, name, json_null());
+ if (ret != 0) {
+ DBG_ERR("Unable to add null data blob at key [%s]\n",
+ name);
+ return JSON_ERROR;
+ }
+ } else if (src.length == 0) {
+ /* the functions in base64.c will not ``encode'' the empty string */
+ ret = json_add_string(object, name, "");
+ if (ret != 0) {
+ DBG_ERR("Unable to add the empty string to json "
+ "object with key [%s]\n", name);
+ return JSON_ERROR;
+ }
+ } else {
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ const char *s = NULL;
+
+ if (ctx == NULL) {
+ DBG_ERR("Out of memory creating context for base64 "
+ "data field [%s]\n", name);
+ return JSON_ERROR;
+ }
+
+ s = base64_encode_data_blob (ctx, src);
+ if (s == NULL) {
+ DBG_ERR("Out of memory encoding %zu B of data "
+ "as base64 for json key [%s]\n",
+ src.length, name);
+ TALLOC_FREE(ctx);
+ return JSON_ERROR;
+ }
+
+ ret = json_add_string(object, name, s);
+ if (ret != 0) {
+ DBG_ERR("Unable to add base64 encoded data to json "
+ "object with key [%s]\n", name);
+ TALLOC_FREE(ctx);
+ return JSON_ERROR;
+ }
+ TALLOC_FREE(ctx);
+ }
+
+ return ret;
+}
+
/*
* @brief Add a formatted string representation of a guid to a json object.
*
@@ -907,7 +1418,7 @@ int json_add_guid(struct json_object *object,
/*
* @brief Convert a JSON object into a string
*
- * Convert the jsom object into a string suitable for printing on a log line,
+ * Convert the json object into a string suitable for printing on a log line,
* i.e. with no embedded line breaks.
*
* If the object is invalid it logs an error and returns NULL.
@@ -953,6 +1464,58 @@ char *json_to_string(TALLOC_CTX *mem_ctx, const struct json_object *object)
return json_string;
}
+/*
+ * @brief Decode string as JSON.
+ *
+ * Convert assumed JSON encoded data to a json_object.
+ * If the input is neither an object nor an array, or it is invalid, return an
+ * error status. The destination is only assigned to if the input was
+ * successfully decoded. Otherwise, dst is not mutated. An existing JSON object
+ * is freed before the assignment.
+ *
+ * @param dst A valid struct json_object to store the decoded data.
+ * @param jsdata Raw JSON encoded data to decode.
+ *
+ * @return Status code, zero on success.
+ */
+int json_from_string(struct json_object *dst, const char *jsdata)
+{
+ struct json_object object;
+ json_error_t jserr;
+
+ if (dst == NULL) {
+ DBG_ERR("Invalid destination for decoding JSON, cannot assign "
+ "to NULL\n");
+ return JSON_ERROR;
+ }
+
+ if (json_is_invalid(dst)) {
+ DBG_ERR("Invalid destination for decoding JSON, cannot assign "
+ "to NULL\n");
+ return JSON_ERROR;
+ }
+
+ if (jsdata == NULL) {
+ DBG_ERR("Invalid raw JSON input, cannot operate on NULL\n");
+ return JSON_ERROR;
+ }
+
+ object.root = json_loads(jsdata, 0, &jserr);
+ if (object.root == NULL) {
+ DBG_ERR("Error while decoding data as JSON at position %d:%d "
+ "(%d B): source=%s description=%s\n",
+ jserr.line, jserr.column, jserr.position,
+ jserr.source, jserr.text);
+ return JSON_ERROR;
+ }
+
+ json_free(dst);
+ object.valid = true;
+ *dst = object;
+
+ return 0;
+}
+
/*
* @brief get a json array named "name" from the json object.
*
@@ -1034,4 +1597,315 @@ struct json_object json_get_object(struct json_object *object, const char *name)
}
return o;
}
+
+/*
+ * @brief Decode data reading from file descriptor.
+ *
+ * If the input is neither an object nor an array, or it is invalid, return an
+ * error status. The destination is only assigned to if the input was
+ * successfully decoded. Otherwise, dst is not mutated. An existing JSON object
+ * is freed before the assignment.
+ *
+ * @param dst A valid struct json_object to store the decoded data.
+ * @param jsfd File descriptor to read JSON encoded data from for decoding.
+ *
+ * @return Status code, zero on success.
+ */
+int json_from_FILEp(struct json_object *dst, FILE *jsfh)
+{
+ struct json_object object;
+ json_error_t jserr;
+
+ if (dst == NULL) {
+ DBG_ERR("Invalid destination for decoding JSON, cannot assign "
+ "to NULL\n");
+ return JSON_ERROR;
+ }
+
+ if (jsfh == NULL) {
+ DBG_ERR("Invalid stdio handle passed for reading\n");
+ return JSON_ERROR;
+ }
+
+ object = json_empty_object;
+ object.root = json_loadf(jsfh, 0, &jserr);
+ if (object.root == NULL) {
+ DBG_ERR("error at position %d:%d (%d B) while reading JSON "
+ "object from handle (fd=%d): source=%s description=%s\n",
+ jserr.line, jserr.column, jserr.position,
+ fileno (jsfh), jserr.source, jserr.text);
+ return JSON_ERROR;
+ }
+
+ json_free(dst);
+ object.valid = true;
+ *dst = object;
+
+ return 0;
+}
+
+/*
+ * @brief Test for key in object.
+ *
+ * @param object The JSON object to access.
+ * @param name Key.
+ *
+ * @return true The object and key are valid and the object has the key,
+ * false otherwise.
+ */
+
+bool json_has_key(const struct json_object *object, const char *name)
+{
+ if (object == NULL) {
+ DBG_ERR("Invalid argument, refusing to operate on NULL"
+ "object\n");
+ return false;
+ }
+
+ if (name == NULL) {
+ DBG_ERR("Invalid argument, refusing to operate on NULL "
+ "as key\n");
+ return false;
+ }
+
+ if (json_is_invalid(object)) {
+ DBG_ERR("Invalid argument, refusing to operate on invalid "
+ "object\n");
+ return false;
+ }
+
+ if (!json_is_object(object->root)) {
+ DBG_ERR("refusing to operate on non-object JSON entity of "
+ "type %s\n",
+ json_object_type_to_string(object));
+ return false;
+ }
+
+ return json_object_get(object->root, name) != NULL;
+}
+
+/*
+ * @brief Get a time_t from a JSON object.
+ *
+ * Retrieve a 64 bit integer from a JSON object key and return it as time_t.
+ *
+ * @param object The JSON object to access.
+ * @param name Key.
+ * @param t Pointer to the destination object.
+ *
+ * @return 0 the operation was successful
+ * -1 the operation failed
+ */
+int json_get_time_t(const struct json_object *object,
+ const char *name,
+ time_t *t)
+{
+ int ret = 0;
+ DATA_BLOB rawi64 = { 0 };
+ TALLOC_CTX *ctx = NULL;
+
+ if (t == NULL) {
+ DBG_ERR("Unable to store time_t value [%s]: "
+ "target location is NULL\n",
+ name);
+ return JSON_ERROR;
+ }
+
+ ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ DBG_ERR("Out of memory creating temporary context\n");
+ return JSON_ERROR;
+ }
+
+ /* object and name are validated indirectly here */
+ ret = json_get_base64(ctx, object, name, &rawi64);
+ if (ret != 0) {
+ DBG_ERR("Unable to read field [%s] as base64 encoded data\n",
+ name);
+ TALLOC_FREE(ctx);
+ return JSON_ERROR;
+ }
+
+ if (rawi64.length != sizeof (int64_t)) {
+ DBG_ERR("Invalid decoded base64 encoded field [%s]: content "
+ "length (%zu B) inadequate for int64_t\n",
+ name, rawi64.length);
+ data_blob_free(&rawi64);
+ TALLOC_FREE(ctx);
+ return JSON_ERROR;
+ }
+
+ *t = BVAL((time_t)rawi64.data, 0); /* potentially lossy on 32 bit */
+
+ data_blob_free(&rawi64);
+ TALLOC_FREE(ctx);
+
+ return 0;
+}
+
+/*
+ * @brief Get a SID from a JSON object.
+ *
+ * Retrieve the SID under the key `name' from a json object and store it in
+ * dom_sid. On the JSON end, value is assumed to be of type `string' conforming
+ * to the ``string format syntax'' for SIDs. The destination pointer is only
+ * updated when all input checks succeed.
+ *
+ * @param object The JSON object to access.
+ * @param name Key.
+ * @param sid Pointer to the destination object.
+ *
+ * @return 0 the operation was successful
+ * -1 the operation failed
+ */
+int json_get_sid(const struct json_object *object,
+ const char *name,
+ struct dom_sid *sid)
+{
+ int ret = 0;
+ const char *data = NULL;
+ struct dom_sid tmp = { 0 };
+ TALLOC_CTX *ctx = NULL;
+
+ /* object and name are checked in json_get_string() */
+ if (sid == NULL) {
+ DBG_ERR("Invalid argument, refusing to operate on NULL "
+ "target\n");
+ return JSON_ERROR;
+ }
+
+ ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ DBG_ERR("Out of memory creating temporary context\n");
+ return JSON_ERROR;
+ }
+
+ ret = json_get_string(ctx, object, name, &data);
+ if (ret != 0 || data == NULL) {
+ DBG_ERR("Failed to get string value from JSON object\n");
+ TALLOC_FREE(ctx);
+ return JSON_ERROR;
+ }
+
+ if (!dom_sid_parse(data, &tmp)) {
+ DBG_ERR("Failed to process string value as SID\n");
+ TALLOC_FREE(ctx);
+ return JSON_ERROR;
+ }
+ TALLOC_FREE(ctx);
+
+ *sid = tmp;
+
+ return 0;
+}
+
+/*
+ * @brief Get a GUID from a JSON object.
+ *
+ * Retrieve the GUID under the key `name' from a json object and store it in
+ * dom_sid. On the JSON end, value is assumed to be of type `string' conforming
+ * to the canonical UUID format. The destination object is only updated when
+ * all input checks succeed.
+ *
+ * @param object The JSON object to access.
+ * @param name Key.
+ * @param sid Pointer to the destination object.
+ *
+ * @return 0 the operation was successful
+ * -1 the operation failed
+ */
+int json_get_guid(const struct json_object *object,
+ const char *name,
+ struct GUID *guid)
+{
+ int ret = 0;
+ NTSTATUS status;
+ const char *data = NULL;
+ TALLOC_CTX *ctx = NULL;
+ struct GUID tmp = { 0 };
+
+ /* object and name are checked in json_get_string() */
+ if (guid == NULL) {
+ DBG_ERR("Invalid argument, refusing to operate on NULL "
+ "target\n");
+ return JSON_ERROR;
+ }
+
+ ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ DBG_ERR("Out of memory creating temporary context\n");
+ return JSON_ERROR;
+ }
+
+ ret = json_get_string(ctx, object, name, &data);
+ if (ret != 0 || data == NULL) {
+ DBG_ERR("Failed to get string value from JSON object\n");
+ TALLOC_FREE(ctx);
+ return JSON_ERROR;
+ }
+
+ status = GUID_from_string (data, &tmp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to process string value [%s] -> [%s] as GUID\n",
+ name, data);
+ TALLOC_FREE(ctx);
+ return JSON_ERROR;
+ }
+ TALLOC_FREE(ctx);
+
+ *guid = tmp;
+
+ return 0;
+}
+
+/*
+ * @brief Get base64 encoded data from a JSON object.
+ *
+ * Retrieve the JSON string member at `key' and return its decoded value
+ * as binary data.
+ *
+ * @param object The JSON object to access.
+ * @param name Key.
+ * @param value Pointer to the destination blob.
+ *
+ * @return 0 the operation was successful
+ * -1 the operation failed
+ */
+int json_get_base64(TALLOC_CTX *ctx,
+ const struct json_object *object,
+ const char *name,
+ DATA_BLOB *dst)
+{
+ int ret = 0;
+ const char *raw = NULL;
+ DATA_BLOB tmp = { 0 };
+
+ /* object and name are checked in json_get_string() */
+ if (dst == NULL) {
+ DBG_ERR("Invalid argument, refusing to operate on NULL "
+ "target\n");
+ return JSON_ERROR;
+ }
+
+ ret = json_get_string(ctx, object, name, &raw);
+ if (ret != 0 || raw == NULL) {
+ DBG_ERR("Failed to get string value from JSON object\n");
+ return JSON_ERROR;
+ }
+
+ if (raw [0] == '\0') {
+ *dst = data_blob_const(NULL, 0);
+ return 0;
+ }
+
+ tmp = base64_decode_data_blob_talloc(ctx, raw);
+ if (tmp.data == NULL && tmp.length == 0) {
+ DBG_ERR("Failed to decode string at key [%s] as base64", name);
+ return JSON_ERROR;
+ }
+
+ *dst = tmp;
+
+ return 0;
+}
#endif
diff --git a/lib/audit_logging/audit_logging.h b/lib/audit_logging/audit_logging.h
index 86e9134a86a..7785b816de6 100644
--- a/lib/audit_logging/audit_logging.h
+++ b/lib/audit_logging/audit_logging.h
@@ -22,6 +22,7 @@
#include "lib/messaging/irpc.h"
#include "lib/tsocket/tsocket.h"
#include "lib/util/attr.h"
+#include "lib/util/data_blob.h"
_WARN_UNUSED_RESULT_ char *audit_get_timestamp(TALLOC_CTX *frame);
void audit_log_human_text(const char *prefix,
@@ -52,6 +53,7 @@ void audit_message_send(struct imessaging_context *msg_ctx,
struct json_object *message);
_WARN_UNUSED_RESULT_ struct json_object json_new_object(void);
_WARN_UNUSED_RESULT_ struct json_object json_new_array(void);
+_WARN_UNUSED_RESULT_ struct json_object json_new_null(void);
void json_free(struct json_object *object);
void json_assert_is_array(struct json_object *array);
_WARN_UNUSED_RESULT_ bool json_is_invalid(const struct json_object *object);
@@ -59,6 +61,15 @@ _WARN_UNUSED_RESULT_ bool json_is_invalid(const struct json_object *object);
_WARN_UNUSED_RESULT_ int json_add_int(struct json_object *object,
const char *name,
const int value);
+_WARN_UNUSED_RESULT_ int json_add_uint64(struct json_object *object,
+ const char *name,
+ const uint64_t value);
+_WARN_UNUSED_RESULT_ int json_add_uint32(struct json_object *object,
+ const char *name,
+ const uint32_t value);
+_WARN_UNUSED_RESULT_ int json_add_time_t(struct json_object *object,
+ const char *name,
+ const time_t t);
_WARN_UNUSED_RESULT_ int json_add_bool(struct json_object *object,
const char *name,
const bool value);
@@ -86,6 +97,34 @@ _WARN_UNUSED_RESULT_ int json_add_sid(struct json_object *object,
_WARN_UNUSED_RESULT_ int json_add_guid(struct json_object *object,
const char *name,
const struct GUID *guid);
+_WARN_UNUSED_RESULT_ int json_add_base64(struct json_object *object,
+ const char *name,
+ const DATA_BLOB src);
+_WARN_UNUSED_RESULT_ int json_get_int(const struct json_object *object,
+ const char *name, int *value);
+_WARN_UNUSED_RESULT_ int json_get_uint64(const struct json_object *object,
+ const char *name, uint64_t *value);
+_WARN_UNUSED_RESULT_ int json_get_uint32(const struct json_object *object,
+ const char *name, uint32_t *value);
+_WARN_UNUSED_RESULT_ int json_get_time_t(const struct json_object *object,
+ const char *name, time_t *t);
+_WARN_UNUSED_RESULT_ int json_get_string(TALLOC_CTX *ctx,
+ const struct json_object *object,
+ const char *name,
+ const char **value);
+_WARN_UNUSED_RESULT_ int json_get_sid(const struct json_object *object,
+ const char *name,
+ struct dom_sid *sid);
+_WARN_UNUSED_RESULT_ int json_get_guid(const struct json_object *object,
+ const char *name,
+ struct GUID *guid);
+_WARN_UNUSED_RESULT_ int json_get_base64(TALLOC_CTX *ctx,
+ const struct json_object *object,
+ const char *name,
+ DATA_BLOB *dst);
+
+_WARN_UNUSED_RESULT_ bool json_has_key(const struct json_object *object,
+ const char *name);
_WARN_UNUSED_RESULT_ struct json_object json_get_array(
struct json_object *object, const char *name);
@@ -93,5 +132,9 @@ _WARN_UNUSED_RESULT_ struct json_object json_get_object(
struct json_object *object, const char *name);
_WARN_UNUSED_RESULT_ char *json_to_string(TALLOC_CTX *mem_ctx,
const struct json_object *object);
+
+_WARN_UNUSED_RESULT_ int json_from_FILEp(struct json_object *dst, FILE *jsfh);
+_WARN_UNUSED_RESULT_ int json_from_string(struct json_object *dst,
+ const char *jsdata);
#endif
#endif
--
2.17.2
From 9941e79d4528b34c744f10f7ca9ebe57af5f328f Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Thu, 4 Oct 2018 09:44:33 +0200
Subject: [RFC PATCH 03/10] lib/audit_logging: unit test more JSON helpers
Unit test additional JSON object helpers:
- json_has_key(),
- json_from_string(),
- json_from_FILEp(),
- json_get_string(),
- json_get_sid(),
- json_add_uint32(), json_get_uint32(),
- json_add_uint64(), json_get_uint64(),
- json_add_time_t(), json_get_time_t(),
- json_get_base64(), and json_add_base64().
Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
lib/audit_logging/tests/audit_logging_test.c | 588 +++++++++++++++++++
1 file changed, 588 insertions(+)
diff --git a/lib/audit_logging/tests/audit_logging_test.c b/lib/audit_logging/tests/audit_logging_test.c
index acd2a4f697f..58aefe1716b 100644
--- a/lib/audit_logging/tests/audit_logging_test.c
+++ b/lib/audit_logging/tests/audit_logging_test.c
@@ -43,7 +43,13 @@
#include <setjmp.h>
#include <cmocka.h>
+#include <limits.h>
#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
#include <time.h>
#include <tevent.h>
#include <config.h>
@@ -101,6 +107,216 @@ static void test_json_add_int(void **state)
assert_int_equal(JSON_ERROR, rc);
}
+#define TEST_JSON_UINT32_NVALUES 3
+static struct { const char *k; uint32_t v; }
+ testme_u32 [TEST_JSON_UINT32_NVALUES] =
+ { { "zero", 0 }
+ , { "one" , 1 }
+ , { "max" , UINT32_MAX }
+ };
+
+static void test_json_add_uint32(void **state)
+{
+ struct json_object object;
+ int rc = 0;
+ int i;
+
+ object = json_new_object();
+ assert_true(object.valid);
+
+ for (i = 0; i < TEST_JSON_UINT32_NVALUES; ++i) {
+ rc = json_add_uint32(&object, testme_u32[i].k, testme_u32[i].v);
+ assert_int_equal(0, rc);
+ }
+
+ assert_int_equal(TEST_JSON_UINT32_NVALUES,
+ json_object_size(object.root));
+
+ object.valid = false;
+ rc = json_add_uint32(&object, "should fail 1", 0xf1u);
+ assert_int_equal(JSON_ERROR, rc);
+
+ json_free(&object);
+
+ rc = json_add_uint32(&object, "should fail 2", 0xf2u);
+ assert_int_equal(JSON_ERROR, rc);
+}
+
+static void test_json_get_uint32(void **state)
+{
+ struct json_object object;
+ int rc = 0;
+ uint32_t v;
+ int i;
+
+ object = json_new_object();
+ assert_true(object.valid);
+
+ for (i = 0; i < TEST_JSON_UINT32_NVALUES; ++i) {
+ rc = json_add_uint32(&object, testme_u32[i].k, testme_u32[i].v);
+ assert_int_equal(0, rc);
+ }
+
+ assert_int_equal(TEST_JSON_UINT32_NVALUES,
+ json_object_size(object.root));
+
+ for (i = 0; i < TEST_JSON_UINT32_NVALUES; ++i) {
+ rc = json_get_uint32(&object, testme_u32[i].k, &v);
+ assert_int_equal(0, rc);
+ assert_int_equal(v, testme_u32[i].v);
+ }
+
+ object.valid = false;
+ rc = json_get_uint32(&object, "should fail 1", &v);
+ assert_int_equal(JSON_ERROR, rc);
+
+ json_free(&object);
+
+ rc = json_get_uint32(&object, "should fail 2", &v);
+ assert_int_equal(JSON_ERROR, rc);
+}
+
+#define TEST_JSON_UINT64_NVALUES 3
+static struct { const char *k; uint64_t v; }
+ testme_u64 [TEST_JSON_UINT64_NVALUES] =
+ { { "zero", 0 }
+ , { "one" , 1 }
+ , { "max" , UINT64_MAX }
+ };
+
+static void test_json_add_uint64(void **state)
+{
+ struct json_object object;
+ int rc = 0;
+ int i;
+
+ object = json_new_object();
+ assert_true(object.valid);
+
+ for (i = 0; i < TEST_JSON_UINT64_NVALUES; ++i) {
+ rc = json_add_uint64(&object, testme_u64[i].k, testme_u64[i].v);
+ assert_int_equal(0, rc);
+ }
+
+ assert_int_equal(TEST_JSON_UINT64_NVALUES,
+ json_object_size(object.root));
+
+ object.valid = false;
+ rc = json_add_uint64(&object, "should fail 1", 0xf1ull);
+ assert_int_equal(JSON_ERROR, rc);
+
+ json_free(&object);
+
+ rc = json_add_uint64(&object, "should fail 2", 0xf2ull);
+ assert_int_equal(JSON_ERROR, rc);
+}
+
+static void test_json_get_uint64(void **state)
+{
+ struct json_object object;
+ int rc = 0;
+ uint64_t v;
+ DATA_BLOB tmp = { 0 };
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ int i;
+
+ object = json_new_object();
+ assert_true(object.valid);
+
+ for (i = 0; i < TEST_JSON_UINT64_NVALUES; ++i) {
+ rc = json_add_uint64(&object, testme_u64[i].k, testme_u64[i].v);
+ assert_int_equal(0, rc);
+ }
+
+ assert_int_equal(TEST_JSON_UINT64_NVALUES,
+ json_object_size(object.root));
+
+ /* validate values as decoded */
+ for (i = 0; i < TEST_JSON_UINT64_NVALUES; ++i) {
+ rc = json_get_uint64(&object, testme_u64[i].k, &v);
+ assert_int_equal(0, rc);
+ assert_memory_equal(&v, &testme_u64[i].v, sizeof(v));
+ }
+
+ /*
+ * validate base64-well formedness of entries created to guard against
+ * accidental encoding as json numbers
+ */
+ for (i = 0; i < TEST_JSON_UINT64_NVALUES; ++i) {
+ rc = json_get_base64(ctx, &object, testme_u64[i].k, &tmp);
+ assert_int_equal(0, rc);
+ assert_int_equal(sizeof(uint64_t), tmp.length);
+ data_blob_free(&tmp);
+ }
+
+ object.valid = false;
+ rc = json_get_uint64(&object, "should fail 1", &v);
+ assert_int_equal(JSON_ERROR, rc);
+
+ json_free(&object);
+
+ rc = json_get_uint64(&object, "should fail 2", &v);
+ assert_int_equal(JSON_ERROR, rc);
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_json_add_time_t(void **state)
+{
+ struct json_object object;
+ int rc = 0;
+
+ object = json_new_object();
+ assert_true(object.valid);
+
+ rc = json_add_time_t(&object, "t_0", time (NULL));
+ assert_int_equal(0, rc);
+ assert_int_equal(1, json_object_size(object.root));
+
+ rc = json_add_time_t(&object, "t_1", LONG_LONG_MAX);
+ assert_int_equal(0, rc);
+ assert_int_equal(2, json_object_size(object.root));
+
+ json_free(&object);
+}
+
+static void test_json_get_time_t(void **state)
+{
+ struct json_object object;
+ int rc = 0;
+ time_t t_0, t;
+
+ object = json_new_object();
+ assert_true(object.valid);
+
+ t_0 = time (NULL);
+ rc = json_add_time_t(&object, "t_0", t_0);
+ assert_int_equal(0, rc);
+ assert_int_equal(1, json_object_size(object.root));
+
+ rc = json_add_time_t(&object, "t_1", LONG_LONG_MAX);
+ assert_int_equal(0, rc);
+ assert_int_equal(2, json_object_size(object.root));
+
+ rc = json_add_time_t(&object, "t_2", LONG_LONG_MIN);
+ assert_int_equal(0, rc);
+ assert_int_equal(3, json_object_size(object.root));
+
+ rc = json_get_time_t(&object, "t_0", &t);
+ assert_int_equal(0, rc);
+ assert_int_equal(t, t_0);
+
+ rc = json_get_time_t(&object, "t_1", &t);
+ assert_int_equal(0, rc);
+ assert_int_equal(t, LONG_LONG_MAX);
+
+ rc = json_get_time_t(&object, "t_2", &t);
+ assert_int_equal(0, rc);
+ assert_int_equal(t, LONG_LONG_MIN);
+
+ json_free(&object);
+}
+
static void test_json_add_bool(void **state)
{
struct json_object object;
@@ -173,6 +389,58 @@ static void test_json_add_string(void **state)
assert_int_equal(JSON_ERROR, rc);
}
+static void test_json_get_string(void **state)
+{
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ struct json_object object;
+ const char *testdata = "{\"test_int\" : 42"
+ ",\"test_string\" : \"ook\""
+ ",\"test_null\" : null"
+ "}";
+ const char *res = NULL;
+
+ object = json_new_null();
+
+ assert_int_equal(0, json_from_string(&object, testdata));
+ assert_non_null(object.root);
+ assert_true(json_is_object(object.root));
+
+ assert_int_equal(0, json_get_string(ctx, &object, "test_string", &res));
+ assert_non_null(res);
+ assert_string_equal(res, "ook");
+
+ res = NULL;
+ assert_int_equal(JSON_ERROR,
+ json_get_string(ctx, &object, "test_int", &res));
+ assert_null(res);
+
+ assert_int_equal(JSON_ERROR,
+ json_get_string(ctx, &object, "test_null", &res));
+ assert_null(res);
+
+ assert_int_equal(JSON_ERROR, json_get_string(ctx, &object, "enoent", &res));
+ assert_null(res);
+
+ res = "notnull";
+ assert_int_equal(JSON_ERROR, json_get_string(ctx, &object, NULL, &res));
+ assert_string_equal(res, "notnull");
+
+ assert_int_equal(JSON_ERROR,
+ json_get_string(ctx, NULL, "test_string", &res));
+ assert_string_equal(res, "notnull");
+
+ assert_int_equal(JSON_ERROR,
+ json_get_string(ctx, &object, "test_string", NULL));
+
+ json_free(&object);
+
+ assert_int_equal(JSON_ERROR,
+ json_get_string(ctx, &object, "test_string", &res));
+ assert_string_equal(res, "notnull");
+
+ TALLOC_FREE(ctx);
+}
+
static void test_json_add_object(void **state)
{
struct json_object object;
@@ -560,6 +828,37 @@ static void test_json_add_sid(void **state)
assert_int_equal(JSON_ERROR, rc);
}
+static void test_json_get_sid(void **state)
+{
+ struct json_object object;
+ const char *testdata = "{\"null\":\"S-1-0-0\""
+ ",\"allauth\":\"S-1-5-42-42-42-42-42-42-42-42"
+ "-42-42-42-42-42-42-42\""
+ "}";
+ struct dom_sid sid;
+ struct dom_sid cmp;
+
+ object = json_new_null();
+
+ assert_int_equal(0, json_from_string(&object, testdata));
+ assert_non_null(object.root);
+ assert_true(json_is_object(object.root));
+
+ assert_int_equal(0, json_get_sid(&object, "null", &sid));
+ assert_true(dom_sid_equal(&sid, &global_sid_NULL));
+
+ assert_int_equal(0, json_get_sid(&object, "allauth", &sid));
+ assert_true(string_to_sid(&cmp, "S-1-5-42-42-42-42-42-42-42-42"
+ "-42-42-42-42-42-42-42"));
+ assert_true(dom_sid_equal(&sid, &cmp));
+
+ assert_int_equal(JSON_ERROR, json_get_sid(&object, "enoent", &sid));
+ assert_int_equal(JSON_ERROR, json_get_sid(&object, "null", NULL));
+ assert_int_equal(JSON_ERROR, json_get_sid(&object, NULL, &sid));
+
+ json_free(&object);
+}
+
static void test_json_add_guid(void **state)
{
struct json_object object;
@@ -600,6 +899,123 @@ static void test_json_add_guid(void **state)
assert_int_equal(JSON_ERROR, rc);
}
+static void test_json_add_base64(void **state)
+{
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ int rc;
+ struct json_object object;
+ struct json_t *value = NULL;
+ const char *testdata = "foo";
+ const char *tmp = NULL;
+
+ object = json_new_object();
+
+ rc = json_add_base64(&object, "test",
+ (DATA_BLOB)
+ { .data = discard_const_p (uint8_t, testdata)
+ , .length = strlen(testdata) });
+ assert_int_equal(0, rc);
+
+ value = json_object_get(object.root, "test");
+ assert_true(json_is_string(value));
+
+ rc = json_get_string(ctx, &object, "test", &tmp);
+ assert_int_equal(0, rc);
+ assert_non_null(tmp);
+ assert_string_equal(tmp, "Zm9v");
+
+ json_free(&object);
+
+ /* empty data -> empty encoded */
+ object = json_new_object();
+ rc = json_add_base64(&object, "test",
+ (DATA_BLOB)
+ { .data = discard_const_p (uint8_t, testdata)
+ , .length = 0 });
+ assert_int_equal(0, rc);
+
+ rc = json_get_string(ctx, &object, "test", &tmp);
+ assert_int_equal(0, rc);
+ assert_non_null(tmp);
+ assert_int_equal(strlen(tmp), 0);
+
+ json_free(&object);
+
+ rc = json_add_base64(&object, "freed_object",
+ (DATA_BLOB)
+ { .data = discard_const_p (uint8_t, testdata)
+ , .length = strlen(testdata) });
+ assert_int_equal(JSON_ERROR, rc);
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_json_has_key(void **state)
+{
+ int rc;
+ struct json_object object;
+ const char *testdata = "{\"klucz\": true"
+ ",\"key\": \"exists\"}";
+
+ object = json_new_null();
+
+ rc = json_from_string(&object, testdata);
+ assert_int_equal(0, rc);
+ assert_false(json_is_invalid(&object));
+
+ assert_true(json_has_key(&object, "key"));
+ assert_true(json_has_key(&object, "klucz"));
+ assert_false(json_has_key(&object, "clef"));
+ assert_false(json_has_key(&object, ""));
+ assert_false(json_has_key(&object, NULL));
+ assert_false(json_has_key(NULL, NULL));
+
+ json_free(&object);
+ assert_false(json_has_key(&object, "key"));
+}
+
+static void test_json_get_base64(void **state)
+{
+ int rc;
+ struct json_object object;
+ const char *testdata = "{\"test\":\"Zm9vAA==\""
+ ",\"empty\":\"\""
+ ",\"junk\":\"** MALFORMED **\""
+ "}";
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ DATA_BLOB tmp = { 0 };
+
+ object = json_new_null();
+
+ rc = json_from_string(&object, testdata);
+ assert_int_equal(0, rc);
+ assert_non_null(object.root);
+ assert_true(json_is_object(object.root));
+
+ rc = json_get_base64(ctx, &object, "test", &tmp);
+ assert_int_equal(0, rc);
+ assert_non_null(tmp.data);
+ assert_int_equal(tmp.length, 4);
+
+ assert_string_equal(tmp.data, "foo");
+ data_blob_free(&tmp);
+
+ rc = json_get_base64(ctx, &object, "empty", &tmp);
+ assert_int_equal(0, rc);
+ assert_int_equal(tmp.length, 0);
+ assert_null(tmp.data);
+ data_blob_free(&tmp);
+
+ rc = json_get_base64(ctx, &object, "junk", &tmp);
+ assert_int_equal(JSON_ERROR, rc);
+ assert_int_equal(tmp.length, 0);
+ assert_null(tmp.data);
+ data_blob_free(&tmp);
+
+ json_free(&object);
+ TALLOC_FREE(ctx);
+}
+
static void test_json_to_string(void **state)
{
struct json_object object;
@@ -634,6 +1050,165 @@ static void test_json_to_string(void **state)
TALLOC_FREE(ctx);
}
+static void test_json_from_string(void **state)
+{
+ int rc;
+ struct json_object object;
+ /* decodable: object, array */
+ const char *testobj = "{\"test_int\" : 42"
+ ",\"test_string\" : \"ook\""
+ ",\"test_null\" : null"
+ "}";
+ const char *testarr = "[ 42, 13.37 ]";
+ /* undecodable: primitives */
+ const char *testint = "42";
+ const char *teststring = "\"ook\"";
+ const char *testnull = "null";
+ /* bad inputs */
+ const char *testjunk1 = "{\"unbalanced\" : 42"
+ ", UNEXPECTED_EOF";
+ const char *testjunk2 = "{ 42: \"malformed\" }";
+
+ object = json_new_null();
+
+ rc = json_from_string(&object, testobj);
+ assert_int_equal(0, rc);
+ assert_non_null(object.root);
+ assert_true(json_is_object(object.root));
+
+ assert_non_null(json_object_get(object.root, "test_int"));
+ assert_non_null(json_object_get(object.root, "test_string"));
+ assert_non_null(json_object_get(object.root, "test_null"));
+
+ json_free(&object);
+ object = json_new_null();
+
+ rc = json_from_string(&object, testarr);
+ assert_int_equal(0, rc);
+ assert_non_null(object.root);
+ assert_true(json_is_array(object.root));
+ assert_int_equal(2, json_array_size(object.root));
+
+ assert_true(json_is_integer(json_array_get(object.root, 0)));
+ assert_true(json_is_real(json_array_get(object.root, 1)));
+
+ json_free(&object);
+ object = json_new_null();
+
+ rc = json_from_string(&object, testint);
+ assert_int_equal(JSON_ERROR, rc);
+
+ rc = json_from_string(&object, teststring);
+ assert_int_equal(JSON_ERROR, rc);
+
+ rc = json_from_string(&object, testnull);
+
+ json_free(&object);
+ object = json_new_object();
+
+ rc = json_from_string(&object, testjunk1);
+ assert_int_equal(JSON_ERROR, rc);
+
+ json_free(&object);
+ object = json_new_object();
+
+ rc = json_from_string(&object, testjunk2);
+ assert_int_equal(JSON_ERROR, rc);
+
+ json_free(&object);
+}
+
+/*
+ * Note on below test: we use posix shm here because of its availability
+ * on older kernels; memfd_create(2) would be even simpler to use but it
+ * is also the more recent API.
+ */
+static void test_json_from_FILEp(void **state)
+{
+ int rc;
+ int fd;
+ FILE *fh;
+ ssize_t sz;
+ struct json_object object;
+ const char *testdata = "{\"test_int\" : 42"
+ ",\"test_string\" : \"ook\""
+ ",\"test_null\" : null"
+ "}";
+ const char *testjunk1 = "{\"unbalanced\" : 42"
+ ", UNEXPECTED_EOF";
+ const char *testjunk2 = "{ 42: \"malformed\" }";
+
+ fd = shm_open("samba-test", O_RDWR | O_CREAT | O_TRUNC, 0600);
+ assert_true(fd != -1);
+ (void)shm_unlink("samba-test"); /* the fd is sufficient */
+
+ fh = fdopen (dup (fd), "r"); /* dup(2) to preserve the fd through fclose() */
+ assert_non_null(fh);
+
+ object = json_new_null();
+ assert_false(json_is_invalid(&object));
+
+ /* test empty ``file'' */
+ rc = json_from_FILEp(&object, fh);
+ assert_int_equal(JSON_ERROR, rc);
+ assert_false(json_is_invalid(&object));
+
+ (void)fclose(fh);
+
+ /* test with good data */
+ sz = write(fd, testdata, strlen(testdata));
+ assert_int_equal(sz, (ssize_t)strlen(testdata));
+ (void)lseek(fd, 0, SEEK_SET);
+
+ fh = fdopen (dup (fd), "r");
+ assert_non_null(fh);
+
+ rc = json_from_FILEp(&object, fh);
+ assert_int_equal(0, rc);
+ assert_non_null(object.root);
+ assert_true(json_is_object(object.root));
+
+ (void)fclose(fh);
+ assert_int_equal(ftruncate(fd, 0), 0);
+
+ /* test with garbage */
+ sz = write(fd, testjunk1, strlen(testdata));
+ assert_int_equal(sz, (ssize_t)strlen(testdata));
+ (void)lseek(fd, 0, SEEK_SET);
+
+ fh = fdopen (dup (fd), "r");
+ assert_non_null(fh);
+
+ rc = json_from_FILEp(&object, fh);
+ assert_int_equal(JSON_ERROR, rc);
+ assert_false(json_is_invalid(&object));
+ assert_true(json_is_object(object.root));
+
+ (void)fclose(fh);
+ assert_int_equal(ftruncate(fd, 0), 0);
+
+ sz = write(fd, testjunk2, strlen(testdata));
+ assert_int_equal(sz, (ssize_t)strlen(testdata));
+ (void)lseek(fd, 0, SEEK_SET);
+
+ fh = fdopen (dup (fd), "r");
+ assert_non_null(fh);
+
+ rc = json_from_FILEp(&object, fh);
+ assert_int_equal(JSON_ERROR, rc);
+ assert_false(json_is_invalid(&object));
+ assert_true(json_is_object(object.root));
+
+ (void)fclose(fh);
+ (void)close(fd);
+
+ assert_non_null(json_object_get(object.root, "test_int"));
+ assert_non_null(json_object_get(object.root, "test_string"));
+ assert_non_null(json_object_get(object.root, "test_null"));
+
+ json_free(&object);
+}
+
static void test_json_get_array(void **state)
{
struct json_object object;
@@ -833,8 +1408,15 @@ int main(int argc, const char **argv)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_json_add_int),
+ cmocka_unit_test(test_json_add_uint32),
+ cmocka_unit_test(test_json_get_uint32),
+ cmocka_unit_test(test_json_add_uint64),
+ cmocka_unit_test(test_json_get_uint64),
+ cmocka_unit_test(test_json_add_time_t),
+ cmocka_unit_test(test_json_get_time_t),
cmocka_unit_test(test_json_add_bool),
cmocka_unit_test(test_json_add_string),
+ cmocka_unit_test(test_json_get_string),
cmocka_unit_test(test_json_add_object),
cmocka_unit_test(test_json_add_to_array),
cmocka_unit_test(test_json_add_timestamp),
@@ -842,10 +1424,16 @@ int main(int argc, const char **argv)
cmocka_unit_test(test_json_add_version),
cmocka_unit_test(test_json_add_address),
cmocka_unit_test(test_json_add_sid),
+ cmocka_unit_test(test_json_get_sid),
cmocka_unit_test(test_json_add_guid),
+ cmocka_unit_test(test_json_add_base64),
+ cmocka_unit_test(test_json_get_base64),
cmocka_unit_test(test_json_to_string),
+ cmocka_unit_test(test_json_from_string),
+ cmocka_unit_test(test_json_from_FILEp),
cmocka_unit_test(test_json_get_array),
cmocka_unit_test(test_json_get_object),
+ cmocka_unit_test(test_json_has_key),
cmocka_unit_test(test_audit_get_timestamp),
};
--
2.17.2
From fe9dfa4bead877f1b74aab4b8beea50a61ae3a48 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Fri, 14 Dec 2018 10:16:18 +0100
Subject: [RFC PATCH 04/10] s3: net: add json printer to `net primarytrust`
Dump the contents of the domain info and credentials (struct
secrets_domain_info1) as nested JSON. This allows saving that
information externally so it can be restored at a later point
without re-joining the host. (The corresponding restore
functionality is subject of a separate patch.)
As with the unstructured version, passing ``--force'' is required
to include the logon credentials in the output.
Naturally this functionality requires Samba to be built with
libjansson.
Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
source3/include/secrets.h | 3 +
source3/passdb/machine_account_secrets.c | 233 +++++++++++++++++++++++
source3/utils/net.c | 26 ++-
3 files changed, 256 insertions(+), 6 deletions(-)
diff --git a/source3/include/secrets.h b/source3/include/secrets.h
index 24ae5bd0664..400ac8987c7 100644
--- a/source3/include/secrets.h
+++ b/source3/include/secrets.h
@@ -119,6 +119,9 @@ void secrets_debug_domain_info(int lvl, const struct secrets_domain_info1 *info,
const char *name);
char *secrets_domain_info_string(TALLOC_CTX *mem_ctx, const struct secrets_domain_info1 *info1,
const char *name, bool include_secrets);
+char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
+ const struct secrets_domain_info1 *info1,
+ const char *name, bool include_secrets);
NTSTATUS secrets_fetch_or_upgrade_domain_info(const char *domain,
TALLOC_CTX *mem_ctx,
struct secrets_domain_info1 **pinfo);
diff --git a/source3/passdb/machine_account_secrets.c b/source3/passdb/machine_account_secrets.c
index dfc21f295a1..9a756831231 100644
--- a/source3/passdb/machine_account_secrets.c
+++ b/source3/passdb/machine_account_secrets.c
@@ -42,6 +42,10 @@
#undef DBGC_CLASS
#define DBGC_CLASS DBGC_PASSDB
+#ifdef HAVE_JANSSON
+#include "audit_logging.h"
+#endif /* [HAVE_JANSSON] */
+
static char *domain_info_keystr(const char *domain);
static char *des_salt_key(const char *realm);
@@ -815,6 +819,235 @@ char *secrets_domain_info_string(TALLOC_CTX *mem_ctx, const struct secrets_domai
return ret;
}
+#ifdef HAVE_JANSSON
+static bool json_add_secrets_domain_info1_password
+ (struct json_object *jsdst,
+ const char *key,
+ const struct secrets_domain_info1_password *pw)
+{
+ int ret = 0;
+ struct json_object jsobj = json_new_object();
+
+ if (json_is_invalid(&jsobj)) {
+ DBG_ERR("refusing to write to invalid JSON object\n");
+
+ return false;
+ }
+
+ if (json_is_invalid(jsdst)) {
+ DBG_ERR("refusing to write to invalid JSON object\n");
+
+ goto failure;
+ }
+
+ if (key == NULL) {
+ DBG_ERR("refusing to access NULL key\n");
+
+ goto failure;
+ }
+
+ ret = json_add_time_t(&jsobj, "Change Time",
+ nt_time_to_unix(pw->change_time));
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsobj, "Change Server", pw->change_server);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ if (pw->change_server != NULL) {
+ ret = json_add_base64(&jsobj, "Cleartext Blob",
+ pw->cleartext_blob);
+ if (ret != 0) {
+ goto failure;
+ }
+ }
+
+ ret = json_add_object(jsdst, key, &jsobj);
+
+ return true;
+failure:
+ json_free(&jsobj);
+ return false;
+}
+
+char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
+ const struct secrets_domain_info1 *info1,
+ const char *name, bool include_secrets)
+{
+ int ret = 0;
+ char *result = NULL;
+ struct json_object jsinfo = json_new_object();
+ struct json_object jsdns = json_new_object();
+
+ if (json_is_invalid(&jsinfo) || json_is_invalid(&jsdns))
+ {
+ DBG_DEBUG("error setting up JSON value\n");
+
+ goto failure;
+ }
+
+ /* XXX: might use flag names */
+ ret = json_add_uint64(&jsinfo, "Reserved Flags", info1->reserved_flags);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_time_t(&jsinfo, "Join Time",
+ nt_time_to_unix(info1->join_time));
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsinfo, "Computer Name", info1->computer_name);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsinfo, "Account Name", info1->account_name);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ /* XXX: might be symbolic name of enum like ndr_misc.c */
+ ret = json_add_int(&jsinfo, "Secure Channel Type",
+ (int)info1->secure_channel_type);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ /* <domain-info> modeled after cmd_lsarpc.c */
+ ret = json_add_string(&jsdns, "Domain NetBios Name",
+ info1->domain_info.name.string);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsdns, "Domain DNS Name",
+ info1->domain_info.dns_domain.string);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsdns, "Domain Forest Name",
+ info1->domain_info.dns_forest.string);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_sid(&jsdns, "Domain SID", info1->domain_info.sid);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_guid(&jsdns, "Domain GUID",
+ &info1->domain_info.domain_guid);
+ if (ret != 0) {
+ goto failure;
+ }
+ /* </domain-info> */
+
+ /* XXX: might use flag names (netlogon.idl) */
+ ret = json_add_uint32(&jsinfo, "Trust Flags", info1->trust_flags);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ /* XXX: might use enum names (lsa.idl) */
+ ret = json_add_int(&jsinfo, "Trust Type", (int)info1->trust_type);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ /* XXX: might use attribute names (lsa.idl) */
+ ret = json_add_uint32(&jsinfo, "Trust Attributes", info1->trust_flags);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ /* NOTE: reserved_routing is unused, cf. secrets.idl */
+
+ /* XXX: might use flag names names (security.idl) */
+ ret = json_add_uint32(&jsinfo, "Supported Encryption Types",
+ info1->supported_enc_types);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_base64(&jsinfo, "Salt Principal",
+ (DATA_BLOB)
+ { .data = discard_const_p(uint8_t,
+ info1->salt_principal)
+ , .length = strlen (info1->salt_principal) });
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_time_t(&jsinfo, "Password Last Change",
+ nt_time_to_unix (info1->password_last_change));
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_uint64(&jsinfo, "Password Changes",
+ info1->password_changes);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ /* XXX: do we want to dump .next_change here? */
+
+ if (include_secrets) {
+ if (info1->password != NULL &&
+ !json_add_secrets_domain_info1_password
+ (&jsinfo, "Password", info1->password))
+ {
+ goto failure;
+ }
+
+ if (info1->old_password != NULL &&
+ !json_add_secrets_domain_info1_password
+ (&jsinfo, "Old Password", info1->old_password))
+ {
+ goto failure;
+ }
+
+ if (info1->older_password != NULL &&
+ !json_add_secrets_domain_info1_password
+ (&jsinfo, "Older Password", info1->older_password))
+ {
+ goto failure;
+ }
+ }
+
+ ret = json_add_object(&jsinfo, "DNS Domain Info", &jsdns);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ result = json_to_string(mem_ctx, &jsinfo);
+ json_free(&jsinfo);
+
+ return result;
+failure:
+ json_free(&jsdns);
+ json_free(&jsinfo);
+
+ return NULL;
+}
+#else /* [HAVE_JANSSON] */
+char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
+ const struct secrets_domain_info1 *info1,
+ const char *name, bool include_secrets)
+{
+ DBG_ERR("JSON support not available\n");
+
+ return NULL;
+}
+#endif /* [HAVE_JANSSON] */
+
static NTSTATUS secrets_store_domain_info1_by_key(const char *key,
const struct secrets_domain_info1 *info1)
{
diff --git a/source3/utils/net.c b/source3/utils/net.c
index 8350e8c0967..86c5e623fed 100644
--- a/source3/utils/net.c
+++ b/source3/utils/net.c
@@ -127,16 +127,30 @@ static int net_primarytrust_dumpinfo(struct net_context *c, int argc,
return 1;
}
- str = secrets_domain_info_string(info, info, domain, include_secrets);
- if (str == NULL) {
- d_fprintf(stderr, "secrets_domain_info_string() failed.\n");
- return 1;
+ if (c->opt_json) {
+ str = secrets_domain_info_json(info, info, domain,
+ include_secrets);
+ if (str == NULL) {
+ d_fprintf(stderr,
+ "secrets_domain_info_json() failed.\n");
+ return 1;
+ }
+ } else {
+ str = secrets_domain_info_string(info, info, domain,
+ include_secrets);
+ if (str == NULL) {
+ d_fprintf(stderr,
+ "secrets_domain_info_string() failed.\n");
+ return 1;
+ }
}
d_printf("%s", str);
if (!c->opt_force) {
- d_printf(_("The password values are only included using "
- "-f flag.\n"));
+ /* on stderr to prevent interleaving with JSON data */
+ d_fprintf(stderr,
+ _("The password values are only included using "
+ "-f flag.\n"));
}
TALLOC_FREE(info);
--
2.17.2
From 38bdf48cf02bbe143686b8cc33b822a920b695c0 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Fri, 14 Dec 2018 17:01:56 +0100
Subject: [RFC PATCH 05/10] s3: net: add primarytrust subcommand `readinfo`
Implement the inverse operation of ``net primarytrust dumpinfo
--json`` to restore an earlier domaininfo dump. `readinfo'
accepts input of the same JSON structure with certain components
being optional: ``Join Time'', ``Supported Encryption Types'',
``Password Last Change'', ``Change Time'', as well as the three
passwords.
As with dumpinfo, the `--force' flag must be passed for secrets
to be injected as well.
Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
source3/include/secrets.h | 5 +
source3/passdb/machine_account_secrets.c | 405 +++++++++++++++++++++++
source3/utils/net.c | 68 ++++
3 files changed, 478 insertions(+)
diff --git a/source3/include/secrets.h b/source3/include/secrets.h
index 400ac8987c7..8078fe53da3 100644
--- a/source3/include/secrets.h
+++ b/source3/include/secrets.h
@@ -125,6 +125,11 @@ char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
NTSTATUS secrets_fetch_or_upgrade_domain_info(const char *domain,
TALLOC_CTX *mem_ctx,
struct secrets_domain_info1 **pinfo);
+struct json_object;
+NTSTATUS secrets_set_domain_info_json(const char *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct json_object *jsdata,
+ bool include_secrets);
NTSTATUS secrets_prepare_password_change(const char *domain, const char *dcname,
const char *cleartext_unix,
TALLOC_CTX *mem_ctx,
diff --git a/source3/passdb/machine_account_secrets.c b/source3/passdb/machine_account_secrets.c
index 9a756831231..efe3b9f766c 100644
--- a/source3/passdb/machine_account_secrets.c
+++ b/source3/passdb/machine_account_secrets.c
@@ -873,6 +873,82 @@ failure:
return false;
}
+static NTSTATUS json_get_secrets_domain_info1_password
+ (TALLOC_CTX *ctx,
+ const struct json_object *jssrc,
+ const char *key,
+ struct secrets_domain_info1_password **dst)
+{
+ int ret;
+ time_t tmp_time;
+ struct secrets_domain_info1_password *pw;
+ struct json_object jspw;
+
+ if (json_is_invalid(jssrc)) {
+ DBG_ERR("password: refusing to read domain password from "
+ "invalid JSON object\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (key == NULL) {
+ DBG_ERR("password: refusing to access NULL key\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (dst == NULL) {
+ DBG_ERR("password: refusing to write to NULL address\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ pw = talloc_zero_size(ctx, sizeof(*pw));
+ if (pw == NULL) {
+ DBG_ERR("password: error allocating memory for domain "
+ "password\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ jspw = json_get_object(discard_const_p (struct json_object, jssrc),
+ key);
+ if (json_is_invalid(&jspw)) {
+ DBG_ERR("password: failed to obtain field [%s] from domain "
+ "info\n", key);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = json_get_time_t(&jspw, "Change Time", &tmp_time);
+ if (ret != 0) {
+ DBG_DEBUG("password: Optional field \"Change Time\" missing or "
+ "invalid; falling back on current time.\n");
+ unix_to_nt_time(&pw->change_time, time(NULL));
+ } else {
+ unix_to_nt_time(&pw->change_time, tmp_time);
+ }
+
+ ret = json_get_string(ctx, &jspw, "Change Server", &pw->change_server);
+ if (ret != 0) {
+ DBG_ERR("password: Field \"Change Server\" missing or "
+ "invalid.\n");
+ goto failure;
+ }
+
+ ret = json_get_base64(ctx, &jspw, "Cleartext Blob",
+ &pw->cleartext_blob);
+ if (ret != 0) {
+ DBG_ERR("password: Field \"Cleartext Blob\" missing or "
+ "invalid.\n");
+ goto failure;
+ }
+
+ *dst = pw;
+
+ return NT_STATUS_OK;
+failure:
+ free(discard_const_p(char,pw->change_server));
+ data_blob_free(&pw->cleartext_blob);
+
+ return NT_STATUS_INVALID_PARAMETER;
+}
+
char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
const struct secrets_domain_info1 *info1,
const char *name, bool include_secrets)
@@ -1530,6 +1606,335 @@ static NTSTATUS secrets_domain_info_password_create(TALLOC_CTX *mem_ctx,
return NT_STATUS_OK;
}
+#ifdef HAVE_JANSSON
+static NTSTATUS
+secrets_json_get_domain_info (TALLOC_CTX *mem_ctx,
+ const struct json_object *jsdata,
+ struct secrets_domain_info1 **dst,
+ bool include_secrets)
+{
+ int ret;
+ NTSTATUS status;
+ struct secrets_domain_info1 *info1;
+ time_t tmp_time;
+ DATA_BLOB tmp_blob = { 0 };
+ struct json_object jsdns;
+
+ info1 = talloc_zero(mem_ctx, struct secrets_domain_info1);
+ if (info1 == NULL) {
+ DBG_ERR("talloc_zero failed for struct secrets_domain_info1\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = json_get_uint64(jsdata, "Reserved Flags", &info1->reserved_flags);
+ if (ret != 0) {
+ DBG_ERR("Mandatory field \"Reserved Flags\" missing or "
+ "invalid.\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = json_get_time_t(jsdata, "Join Time", &tmp_time);
+ if (ret != 0) {
+ DBG_DEBUG("Optional field \"Join Time\" missing or "
+ "invalid; falling back on current time.\n");
+ unix_to_nt_time(&info1->join_time, time(NULL));
+ } else {
+ unix_to_nt_time(&info1->join_time, tmp_time);
+ }
+
+ if (json_has_key (jsdata, "Computer Name")) {
+ ret = json_get_string(mem_ctx, jsdata, "Computer Name",
+ &info1->computer_name);
+ if (ret != 0) {
+ DBG_ERR("Optional field \"Computer Name\" present but "
+ "invalid.\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ } else {
+ DBG_DEBUG("Optional field \"Computer Name\" absent, using "
+ "Netbios name from smb.conf [%s].\n",
+ lp_netbios_name());
+ info1->computer_name = lp_netbios_name();
+ }
+
+ if (json_has_key (jsdata, "Computer Name")) {
+ ret = json_get_string(mem_ctx, jsdata, "Account Name",
+ &info1->account_name);
+ if (ret != 0) {
+ DBG_ERR("Optional field \"Account Name\" present but "
+ "invalid.\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ } else {
+ DBG_DEBUG("Optional field \"Account Name\" absent, using "
+ "computer name [%s] as the base.\n",
+ info1->computer_name);
+ info1->account_name = talloc_asprintf(mem_ctx, "%s$",
+ info1->computer_name);
+ }
+
+ ret = json_get_int(jsdata, "Secure Channel Type",
+ (int *)&info1->secure_channel_type);
+ if (ret != 0) {
+ DBG_ERR("Mandatory field \"Secure Channel Type\" missing or "
+ "invalid.\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = json_get_uint32(jsdata, "Trust Flags", &info1->trust_flags);
+ if (ret != 0) {
+ DBG_ERR("Mandatory field \"Trust Flags\" missing or "
+ "invalid.\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = json_get_int(jsdata, "Trust Type", (int *)&info1->trust_type);
+ if (ret != 0) {
+ DBG_ERR("Mandatory field \"Trust Type\" missing or "
+ "invalid.\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = json_get_uint32(jsdata, "Trust Attributes",
+ &info1->trust_attributes);
+ if (ret != 0) {
+ DBG_ERR("Mandatory field \"Trust Attributes\" missing or "
+ "invalid.\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = json_get_uint32(jsdata, "Supported Encryption Types",
+ &info1->supported_enc_types);
+ if (ret != 0) {
+ DBG_DEBUG("Optional field \"Supported Encryption Types\" "
+ "missing or invalid, falling back on zero.\n");
+ }
+
+ if (json_has_key (jsdata, "Salt Principal")) {
+ ret = json_get_base64(mem_ctx, jsdata, "Salt Principal",
+ &tmp_blob);
+
+ if (ret != 0) {
+ DBG_ERR("Optional field \"Salt Principal\" invalid.\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ } else {
+ const char *nacl = kerberos_secrets_fetch_salt_princ();
+
+ if (nacl == NULL) {
+ DBG_ERR("Failed to fall back on default salt for "
+ "missing optional field \"Salt Principal\".\n");
+
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ tmp_blob = data_blob_string_const(talloc_strdup(mem_ctx, nacl));
+ if (tmp_blob.data == NULL) {
+ DBG_ERR("Out of memory allocating %zu B for default "
+ "salt.\n",
+ strlen (nacl));
+
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ info1->salt_principal =
+ talloc_zero_size(mem_ctx, tmp_blob.length + 1);
+ if (info1->salt_principal == NULL) {
+ DBG_ERR("Out of memory allocating %zu B for salt principal\n",
+ tmp_blob.length + 1);
+ data_blob_free(&tmp_blob);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ memcpy(discard_const_p(char, info1->salt_principal),
+ tmp_blob.data, tmp_blob.length);
+ data_blob_free(&tmp_blob);
+
+ ret = json_get_time_t(jsdata, "Password Last Change", &tmp_time);
+ if (ret != 0) {
+ DBG_DEBUG("Optional field \"Password Last Change\" missing or "
+ "invalid; falling back on current time.\n");
+ unix_to_nt_time(&info1->password_last_change, time(NULL));
+ } else {
+ unix_to_nt_time(&info1->password_last_change, tmp_time);
+ }
+
+ ret = json_get_uint64(jsdata, "Password Changes",
+ &info1->password_changes);
+ if (ret != 0) {
+ DBG_ERR("Mandatory field \"Password Changes\" missing or "
+ "invalid.\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (json_has_key (jsdata, "Password")) {
+ if (!include_secrets) {
+ DBG_ERR("Setting the \"Password\" field requires "
+ "--force\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ status = json_get_secrets_domain_info1_password
+ (mem_ctx, jsdata, "Password", &info1->password);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Optional field \"Password\" invalid\n");
+ return status;
+ }
+ }
+
+ if (json_has_key (jsdata, "Old Password")) {
+ if (!include_secrets) {
+ DBG_ERR("Setting the \"Old Password\" field requires "
+ "--force\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ status = json_get_secrets_domain_info1_password
+ (mem_ctx, jsdata, "Old Password", &info1->old_password);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Optional field \"Old Password\" invalid\n");
+ return status;
+ }
+ }
+
+ if (json_has_key (jsdata, "Older Password")) {
+ if (!include_secrets) {
+ DBG_ERR("Setting the \"Older Password\" field requires "
+ "--force\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = json_get_secrets_domain_info1_password
+ (mem_ctx, jsdata, "Older Password",
+ &info1->older_password);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Optional field \"Older Password\" invalid\n");
+ return status;
+ }
+ }
+
+ jsdns = json_get_object(discard_const_p(struct json_object, jsdata),
+ "DNS Domain Info");
+ if (json_is_invalid(&jsdns)) {
+ DBG_ERR("Mandatory field \"DNS Domain Info\" missing or "
+ "invalid.\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = json_get_string(mem_ctx, &jsdns, "Domain NetBios Name",
+ &info1->domain_info.name.string);
+ if (ret != 0) {
+ DBG_ERR("Mandatory field \"Domain NetBios Name\" missing or "
+ "invalid.\n");
+ json_free(&jsdns);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = json_get_string(mem_ctx, &jsdns, "Domain DNS Name",
+ &info1->domain_info.dns_domain.string);
+ if (ret != 0) {
+ DBG_ERR("Mandatory field \"Domain DNS Name\" missing or "
+ "invalid.\n");
+ json_free(&jsdns);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = json_get_string(mem_ctx, &jsdns, "Domain Forest Name",
+ &info1->domain_info.dns_forest.string);
+ if (ret != 0) {
+ DBG_ERR("Mandatory field \"Domain Forest Name\" missing or "
+ "invalid.\n");
+ json_free(&jsdns);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ info1->domain_info.sid =
+ talloc_zero_size(mem_ctx, sizeof(*info1->domain_info.sid));
+ if (info1->domain_info.sid == NULL) {
+ DBG_ERR("Out of memory allocating storage for domain SID\n");
+ json_free(&jsdns);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = json_get_sid(&jsdns, "Domain SID", info1->domain_info.sid);
+ if (ret != 0) {
+ DBG_ERR("Mandatory field \"Domain SID\" missing or "
+ "invalid.\n");
+ json_free(&jsdns);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = json_get_guid(&jsdns, "Domain GUID",
+ &info1->domain_info.domain_guid);
+ if (ret != 0) {
+ DBG_ERR("Mandatory field \"Domain GUID\" missing or "
+ "invalid.\n");
+ json_free(&jsdns);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ *dst = info1;
+
+ return NT_STATUS_OK;
+}
+#else /* [HAVE_JANSSON] */
+static NTSTATUS
+secrets_json_get_domain_info (TALLOC_CTX *mem_ctx,
+ const struct json_object *jsdata,
+ struct secrets_domain_info1 **dst,
+ bool include_secrets)
+{
+ DBG_ERR("JSON support not available\n");
+
+ return NT_STATUS_NOT_SUPPORTED;
+}
+#endif /* [HAVE_JANSSON] */
+
+NTSTATUS secrets_set_domain_info_json(const char *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct json_object *jsdata,
+ bool include_secrets)
+{
+ int ret;
+ struct secrets_domain_info1 *info1 = NULL;
+ struct db_context *db;
+ NTSTATUS status;
+
+ status = secrets_json_get_domain_info (mem_ctx, jsdata, &info1,
+ include_secrets);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_json_get_domain_info() failed\n");
+ return status;
+ }
+
+ db = secrets_db_ctx();
+ ret = dbwrap_transaction_start(db);
+ if (ret != 0) {
+ DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+ domain);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ secrets_debug_domain_info(DBGLVL_INFO, info1, "set");
+
+ status = secrets_store_domain_info(info1, true /* upgrade */);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_store_domain_info() failed "
+ "for %s - %s\n", domain, nt_errstr(status));
+ dbwrap_transaction_cancel(db);
+ return status;
+ }
+
+ ret = dbwrap_transaction_commit(db);
+ if (ret != 0) {
+ DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+ domain);
+ dbwrap_transaction_cancel(db);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ return status;
+}
+
NTSTATUS secrets_fetch_or_upgrade_domain_info(const char *domain,
TALLOC_CTX *mem_ctx,
struct secrets_domain_info1 **pinfo)
diff --git a/source3/utils/net.c b/source3/utils/net.c
index 86c5e623fed..0604d3b937a 100644
--- a/source3/utils/net.c
+++ b/source3/utils/net.c
@@ -44,6 +44,7 @@
#include "popt_common_cmdline.h"
#include "utils/net.h"
#include "secrets.h"
+#include "librpc/gen_ndr/netlogon.h"
#include "lib/netapi/netapi.h"
#include "../libcli/security/security.h"
#include "passdb.h"
@@ -56,6 +57,11 @@
#include "utils/net_afs.h"
#endif
+#ifdef HAVE_JANSSON
+#include "audit_logging.h"
+#endif /* [HAVE_JANSSON] */
+
+
/***********************************************************************/
/* end of internationalization section */
/***********************************************************************/
@@ -94,6 +100,57 @@ static void set_line_buffering(FILE *f)
setvbuf(f, NULL, _IOLBF, 0);
}
+#ifdef HAVE_JANSSON
+static int net_primarytrust_readinfo(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret = 0;
+ NTSTATUS status = NT_STATUS_OK;
+ int role = lp_server_role();
+ struct json_object jsobj = json_new_object ();
+ const char *domain = lp_workgroup();
+ bool include_secrets = c->opt_force;
+
+ if (role >= ROLE_ACTIVE_DIRECTORY_DC) {
+ d_printf(_("net primarytrust readinfo is only supported "
+ "on a DOMAIN_MEMBER for now.\n"));
+ return 1;
+ }
+
+ if (isatty(STDIN_FILENO) == 1) {
+ set_line_buffering(stderr);
+ /* signal user that net expects some input */
+ d_fprintf(stderr, "hint: waiting for input\n");
+ }
+
+ ret = json_from_FILEp(&jsobj, stdin);
+ if (ret != 0 || json_is_invalid(&jsobj)) {
+ d_fprintf(stderr, _("Error reading JSON from stdin.\n"));
+ return 1;
+ }
+
+ status = secrets_set_domain_info_json(domain, talloc_tos(), &jsobj,
+ include_secrets);
+ json_free (&jsobj);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("Failed to inject the provided domain info.\n"));
+ return 1;
+ }
+
+ return 0;
+}
+#else /* [HAVE_JANSSON] */
+static int net_primarytrust_readinfo(struct net_context *_c, int _argc,
+ const char **_argv)
+{
+ d_fprintf(stderr, _("JSON support not available\n"));
+
+ return -1;
+}
+#endif /* [HAVE_JANSSON] */
+
static int net_primarytrust_dumpinfo(struct net_context *c, int argc,
const char **argv)
{
@@ -179,6 +236,17 @@ static int net_primarytrust(struct net_context *c, int argc, const char **argv)
"in secrets.tdb.\n"
" Requires the -f flag to include the password values.")
},
+ {
+ "readinfo",
+ net_primarytrust_readinfo,
+ NET_TRANSPORT_LOCAL,
+ N_("Set the workstation trust"),
+ N_(" net [options] primarytrust readinfo'\n"
+ " Set the workstation trust from JSON dump "
+ "in secrets.tdb\n."
+ " Requires the -f flag to include the password "
+ "values.")
+ },
{NULL, NULL, 0, NULL, NULL}
};
--
2.17.2
From 37a4ed6230a4d085602764b6d6e7f12aa081d411 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 8 Oct 2018 13:57:42 +0200
Subject: [RFC PATCH 06/10] testprogs/blackbox/subunit.sh: add json output
helper
Add a naive JSON validator to ensure no stray data interleaves
into the structured output.
Depends on jq(1). If the tools is absent, the test is skipped.
Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
testprogs/blackbox/subunit.sh | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/testprogs/blackbox/subunit.sh b/testprogs/blackbox/subunit.sh
index bcc5bd6a928..919d62bce5d 100755
--- a/testprogs/blackbox/subunit.sh
+++ b/testprogs/blackbox/subunit.sh
@@ -115,6 +115,33 @@ testit_grep () {
return $status
}
+if ! command -v jq >/dev/null ; then
+ testit_json () { subunit_skip_test $@ ; }
+else
+ # This returns 0 if the command gave success and the output on stdout
+ # was accepted as JSON by jq; all other cases return != 0
+ testit_json () {
+ name="$1"
+ shift
+ cmdline="$@"
+ subunit_start_test "$name"
+ output=`$cmdline >&1`
+ status=$?
+ if [ x$status != x0 ]; then
+ printf '%s' "$output" | subunit_fail_test "$name"
+ return $status
+ fi
+ printf '%s' "$output" | jq empty
+ gstatus=$?
+ if [ x$gstatus = x0 ]; then
+ subunit_pass_test "$name"
+ else
+ printf 'jq: output failed to parse as JSON:\n%s' "$output" | subunit_fail_test "$name"
+ fi
+ return $status
+ }
+fi
+
testit_expect_failure () {
name="$1"
shift
--
2.17.2
From bd8d6e4a6dbdd4cd81d5c6b562a8a3f55c30c4e5 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 8 Oct 2018 14:00:35 +0200
Subject: [RFC PATCH 07/10] testprogs: blackbox test `net primarytrust
{dump,read}info`
Test wellformedness of --json output and whether primarytrust
readinfo accepts it back as-is.
Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
testprogs/blackbox/test_net_ads.sh | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/testprogs/blackbox/test_net_ads.sh b/testprogs/blackbox/test_net_ads.sh
index d3c4de5b741..7c0eb0a1187 100755
--- a/testprogs/blackbox/test_net_ads.sh
+++ b/testprogs/blackbox/test_net_ads.sh
@@ -60,6 +60,11 @@ testit "test setspn list shows the newly deleted spn ($spn) is gone" test $found
testit "changetrustpw" $VALGRIND $net_tool ads changetrustpw || failed=`expr $failed + 1`
+testit "primarytrust dumpinfo" $VALGRIND $net_tool primarytrust dumpinfo --force || failed=`expr $failed + 1`
+testit_json "primarytrust dumpinfo(json)" $net_tool primarytrust dumpinfo --json --force || failed=`expr $failed + 1`
+dominfo="$($net_tool primarytrust dumpinfo --json --force 2>/dev/null)"
+testit "primarytrust readinfo(json)" $VALGRIND $net_tool primarytrust readinfo --force <<<"${dominfo}" || failed=`expr $failed + 1`
+
testit "leave" $VALGRIND $net_tool ads leave -U$DC_USERNAME%$DC_PASSWORD || failed=`expr $failed + 1`
# Test with kerberos method = secrets and keytab
--
2.17.2
From 3595a4f8b8143a7a7d6d1b738c21311da59a6046 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Thu, 11 Oct 2018 11:06:25 +0200
Subject: [RFC PATCH 08/10] python/samba/tests: add stdin handling helper when
testing commands
Add an extended version ``check_output_with_stdin()`` which takes
a string that is passed to the standard input of the child and
otherwise assigns /dev/null. Under python 3 it supports timeouts
as well.
The rationale for the addition is that ``check_output()'' lacks a
way to control stdin behavior for the forked process. This is
inconvenient when working with binaries that assume interactive
context and prompt for user inputs unexpectedly.
Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
python/samba/tests/__init__.py | 35 ++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py
index 2eaf9785a1d..540fa08220d 100644
--- a/python/samba/tests/__init__.py
+++ b/python/samba/tests/__init__.py
@@ -58,6 +58,10 @@ except ImportError:
BINDIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
"../../../../bin"))
+try:
+ DEVNULL_FILEOBJ = subprocess.DEVNULL
+except AttributeError: # python 2
+ DEVNULL_FILEOBJ = open ("/dev/null", "r")
HEXDUMP_FILTER = bytearray([x if ((len(repr(chr(x))) == 3) and (x < 127)) else ord('.') for x in range(256)])
@@ -430,6 +434,37 @@ class BlackboxTestCase(TestCaseInTempDir):
raise BlackboxProcessError(retcode, line, stdoutdata, stderrdata)
return stdoutdata
+ def check_output_with_stdin(self, line, stdin=None, timeout=None):
+ """
+ Like check_output() above, but with fine-grained control over stdin.
+
+ :param line: Command line to execute (supports only strings for
+ system() on account of the `self._make_cmdline` pass).
+ :param stdin: If *None*, assign `/dev/null` to fd 0; otherwise feed
+ the value (*bytes*) to the child.
+ :param timeout: Timeout in seconds; ignored with python 2.x.
+ :return: *bytes*,
+ """
+
+ input = DEVNULL_FILEOBJ
+ if stdin is not None:
+ input = subprocess.PIPE
+
+ line = self._make_cmdline(line)
+ p = subprocess.Popen(line, stdin=input, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, shell=True,
+ close_fds=True)
+
+ if sys.hexversion < 0x30000000:
+ stdoutdata, stderrdata = p.communicate(input=stdin)
+ else:
+ stdoutdata, stderrdata = p.communicate(input=stdin,
+ timeout=timeout)
+ retcode = p.returncode
+ if retcode != 0:
+ raise BlackboxProcessError(retcode, line, stdoutdata, stderrdata)
+ return stdoutdata
+
# Generate a random password that can be safely passed on the command line
# i.e. it does not contain any shell meta characters.
def random_password(self, count=32):
--
2.17.2
From b3b7cd1b9b32937057682f24dd4e33ef644e8511 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Fri, 12 Oct 2018 14:54:28 +0200
Subject: [RFC PATCH 09/10] python/samba/tests: add simple file hashing helper
Wrap MD5 hashing of files to detect side-effects during blackbox
tests.
Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
python/samba/tests/__init__.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py
index 540fa08220d..60bbad14ac6 100644
--- a/python/samba/tests/__init__.py
+++ b/python/samba/tests/__init__.py
@@ -18,6 +18,7 @@
"""Samba Python tests."""
+import hashlib
import os
import tempfile
import warnings
@@ -476,6 +477,15 @@ class BlackboxTestCase(TestCaseInTempDir):
string.digits) for x in range(count - 3))
return password
+ def hash_file (self, path):
+ md5ctx = hashlib.md5 ()
+ with open (path, "rb") as fh:
+ while True:
+ chunk = fh.read (4096)
+ if chunk == b"": break
+ md5ctx.update (chunk)
+ return md5ctx.digest ()
+
def connect_samdb(samdb_url, lp=None, session_info=None, credentials=None,
flags=0, ldb_options=None, ldap_only=False, global_schema=True):
--
2.17.2
From 822cb5279dc278c2856f6e1b2cd0e31b4a6e9704 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Fri, 5 Oct 2018 11:00:09 +0200
Subject: [RFC PATCH 10/10] tests/blackbox: cover `net primarytrust' in json
tests
Run the existing test variants (well-formedness, matching keys)
on `net primarytrust dumpinfo' as well. Since this extends the
scope of the tests, the script and the test objects are renamed
to something more generic. The added test requires a joined
domain member with machine account credentials present in
secrets.tdb, so move it to the `ad_member:local' suite.
Also add new test script for replaying a domain info dump using
`net primarytrust readinfo'. This test focuses on behavior wrt.
invalid inputs.
Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
.../blackbox/{netads_json.py => net_json.py} | 62 ++--
.../samba/tests/blackbox/netprimarytrust.py | 321 ++++++++++++++++++
source4/selftest/tests.py | 7 +-
3 files changed, 367 insertions(+), 23 deletions(-)
rename python/samba/tests/blackbox/{netads_json.py => net_json.py} (53%)
create mode 100644 python/samba/tests/blackbox/netprimarytrust.py
diff --git a/python/samba/tests/blackbox/netads_json.py b/python/samba/tests/blackbox/net_json.py
similarity index 53%
rename from python/samba/tests/blackbox/netads_json.py
rename to python/samba/tests/blackbox/net_json.py
index 1c254468d36..759b485984f 100644
--- a/python/samba/tests/blackbox/netads_json.py
+++ b/python/samba/tests/blackbox/net_json.py
@@ -1,4 +1,4 @@
-# Blackbox tests for the "net ads ... --json" commands
+# Blackbox tests for the "net ... --json" commands
# Copyright (C) 2018 Intra2net AG
#
# This program is free software; you can redistribute it and/or modify
@@ -21,9 +21,17 @@ import re
import samba.tests
from samba.compat import get_string
-COMMAND = "bin/net ads"
+COMMAND_NET_ADS = "bin/net ads"
+COMMAND_NET_PTRUST = "bin/net primarytrust"
# extract keys from non-json version
-PLAIN_KEY_REGEX = re.compile ("^([^ \t:][^:]*):")
+PLAIN_KEY_REGEX = re.compile ("^([^ \t:][^:]*):")
+
+
+def compat_json_loads (raw):
+ # in some python versions (<=3.4?) json.loads() is picky about the input
+ if isinstance (raw, bytes):
+ raw = raw.decode ("ascii", "ignore")
+ return json.loads (raw)
class BaseWrapper (object):
"""
@@ -31,19 +39,20 @@ class BaseWrapper (object):
being run by unittest directly.
"""
- class NetAdsJSONTests_Base(samba.tests.BlackboxTestCase):
+ class NetJSONTests_Base(samba.tests.BlackboxTestCase):
"""Blackbox tests for JSON output of the net ads suite of commands."""
- subcmd = None
+ command = "/bin/false"
+ subcmd = None
def setUp(self):
- super(BaseWrapper.NetAdsJSONTests_Base, self).setUp()
+ super(BaseWrapper.NetJSONTests_Base, self).setUp()
def test_json_wellformed (self):
"""The output of ``--json`` commands must parse as JSON."""
- argv = "%s %s --json" % (COMMAND, self.subcmd)
+ argv = "%s %s --json" % (self.command, self.subcmd)
try:
- out = self.check_output(argv)
- json.loads (get_string(out))
+ out = self.check_output_with_stdin(argv, stdin=None)
+ compat_json_loads (get_string (out))
except samba.tests.BlackboxProcessError as e:
self.fail("Error calling [%s]: %s" % (argv, e))
@@ -53,32 +62,45 @@ class BaseWrapper (object):
respective plain counterpart.
Does not check nested dictionaries (e. g. the ``Flags`` value of
- ``net ads lookup``..
+ ``net ads lookup``..)
"""
- argv = "%s %s" % (COMMAND, self.subcmd)
+ if self.matching is False: self.skipTest ("test does not apply")
+
+ argv = "%s %s" % (self.command, self.subcmd)
try:
- out_plain = get_string(self.check_output(argv))
+ out_plain = get_string(self.check_output_with_stdin(argv,
+ stdin=None))
except samba.tests.BlackboxProcessError as e:
self.fail("Error calling [%s]: %s" % (argv, e))
- argv = "%s %s --json" % (COMMAND, self.subcmd)
+ argv = "%s %s --json" % (self.command, self.subcmd)
try:
- out_jsobj = self.check_output(argv)
+ out_jsobj = get_string(self.check_output_with_stdin(argv,
+ stdin=None))
except samba.tests.BlackboxProcessError as e:
self.fail("Error calling [%s]: %s" % (argv, e))
- parsed = json.loads (get_string(out_jsobj))
+ parsed = compat_json_loads (get_string (out_jsobj))
for key in [ re.match (PLAIN_KEY_REGEX, line).group(1)
for line in out_plain.split ("\n")
- if line != "" and line [0] not in " \t:" ]:
+ if line != "" and line [0] not in " \t:" ]:
self.assertTrue (parsed.get (key) is not None)
del parsed [key]
self.assertTrue (len (parsed) == 0) # tolerate no leftovers
-class NetAdsJSONInfoTests(BaseWrapper.NetAdsJSONTests_Base):
- subcmd = "info"
+class NetJSON_AdsInfoTests(BaseWrapper.NetJSONTests_Base):
+ command = COMMAND_NET_ADS
+ subcmd = "info"
+ matching = True
+
+class NetJSON_AdsLookupTests(BaseWrapper.NetJSONTests_Base):
+ command = COMMAND_NET_ADS
+ subcmd = "lookup"
+ matching = True
-class NetAdsJSONlookupTests(BaseWrapper.NetAdsJSONTests_Base):
- subcmd = "lookup"
+class NetJSON_PrimarytrustDumpinfoTests(BaseWrapper.NetJSONTests_Base):
+ command = COMMAND_NET_PTRUST
+ subcmd = "dumpinfo"
+ matching = False
diff --git a/python/samba/tests/blackbox/netprimarytrust.py b/python/samba/tests/blackbox/netprimarytrust.py
new file mode 100644
index 00000000000..2ea9ab92b6c
--- /dev/null
+++ b/python/samba/tests/blackbox/netprimarytrust.py
@@ -0,0 +1,321 @@
+# Blackbox tests for the "net primarytrust ... --json" commands
+# Copyright (C) 2018 Intra2net AG
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import json
+import os
+
+import samba.tests
+from samba.compat import get_string
+
+COMMAND = "bin/net primarytrust"
+CHILD_TIMEOUT = 3 # s, don't delay tests expecting input
+SECRETS_PATH_FMT = "%s/%s/private/secrets.tdb"
+
+def compat_json_loads (raw):
+ # in some python versions (<=3.4?) json.loads() is picky about the input
+ if isinstance (raw, bytes):
+ raw = raw.decode ("ascii", "ignore")
+ return json.loads (raw)
+
+DOMINFO_BLOB_OK = b"""
+ { "Reserved Flags": "AAAAAAAAAAA=",
+ "Join Time": "KgAAAAAAAAA=",
+ "Computer Name": "LOCALADMEMBER",
+ "Account Name": "LOCALADMEMBER$",
+ "Secure Channel Type": 2,
+ "Trust Flags": 26,
+ "Trust Type": 2,
+ "Trust Attributes": 26,
+ "Supported Encryption Types": 31,
+ "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
+ "Password Last Change": "NWUTXAAAAAA=",
+ "Password Changes": "AQAAAAAAAAA=",
+ "Password": {
+ "Change Time": "ysIkXAAAAAA=",
+ "Change Server": "ADDC",
+ "Cleartext Blob": "Erzx4o2+ZLrW+kx/dHn+s8Al9i6IYHp5mOLfa7Vi5qB/bZ3hSTyRcSxsguu3A5gE+GAP6mh7cOzDo7njgPUYdzB2qnbi5sVsMznTb3Zgz6ts8R5p+2+W97b2bL4sf445/D/rOkU5pLMAcyG+HbyH9wQ81ng8Ye13nuD+5+i6vXmivG3zqij4veVo6aeob0H6fOOUqzjpzOmHt0w3k3Nl/Efo3KrNsrAtUDpQ+sKxvPNOdqdzCzxWc1esAS8VYxI/T3jPLc11rWcr7y4uJPP0+Dali6XWrnnrZvw3LF25njI2N/7kNPiMK1gner8WaitimG5hMXKu86xWdOYB1rawshF6+Wf2rYNj7bVzNNG2QG2/L/2iLu5N4JqjDSw++39wujr+eR/2S7T/AEpuBjQ=" },
+ "DNS Domain Info": {
+ "Domain NetBios Name": "ADDOMAIN",
+ "Domain DNS Name": "addom.samba.example.com",
+ "Domain Forest Name": "addom.samba.example.com",
+ "Domain SID": "S-1-5-21-42-1337-1701",
+ "Domain GUID": "ec0ef791-e41e-44b7-8990-f05eacb06174" } }
+"""
+
+DOMINFO_BLOB_OK_MISSING_ENCTYPES = b"""
+ { "Reserved Flags": "AAAAAAAAAAA=",
+ "Join Time": "KgAAAAAAAAA=",
+ "Computer Name": "LOCALADMEMBER",
+ "Account Name": "LOCALADMEMBER$",
+ "Secure Channel Type": 2,
+ "Trust Flags": 26,
+ "Trust Type": 2,
+ "Trust Attributes": 26,
+ "Supported Encryption Types": 31,
+ "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
+ "Password Last Change": "NWUTXAAAAAA=",
+ "Password Changes": "AQAAAAAAAAA=",
+ "Password": {
+ "Change Time": "ysIkXAAAAAA=",
+ "Change Server": "ADDC",
+ "Cleartext Blob": "Erzx4o2+ZLrW+kx/dHn+s8Al9i6IYHp5mOLfa7Vi5qB/bZ3hSTyRcSxsguu3A5gE+GAP6mh7cOzDo7njgPUYdzB2qnbi5sVsMznTb3Zgz6ts8R5p+2+W97b2bL4sf445/D/rOkU5pLMAcyG+HbyH9wQ81ng8Ye13nuD+5+i6vXmivG3zqij4veVo6aeob0H6fOOUqzjpzOmHt0w3k3Nl/Efo3KrNsrAtUDpQ+sKxvPNOdqdzCzxWc1esAS8VYxI/T3jPLc11rWcr7y4uJPP0+Dali6XWrnnrZvw3LF25njI2N/7kNPiMK1gner8WaitimG5hMXKu86xWdOYB1rawshF6+Wf2rYNj7bVzNNG2QG2/L/2iLu5N4JqjDSw++39wujr+eR/2S7T/AEpuBjQ=" },
+ "DNS Domain Info": {
+ "Domain NetBios Name": "ADDOMAIN",
+ "Domain DNS Name": "addom.samba.example.com",
+ "Domain Forest Name": "addom.samba.example.com",
+ "Domain SID": "S-1-5-21-42-1337-1701",
+ "Domain GUID": "ec0ef791-e41e-44b7-8990-f05eacb06174" } }
+"""
+
+DOMINFO_BLOB_OK_MISSING_TIMESTAMPS = b"""
+ { "Reserved Flags": "AAAAAAAAAAA=",
+ "Computer Name": "LOCALADMEMBER",
+ "Account Name": "LOCALADMEMBER$",
+ "Secure Channel Type": 2,
+ "Trust Flags": 26,
+ "Trust Type": 2,
+ "Trust Attributes": 26,
+ "Supported Encryption Types": 31,
+ "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
+ "Password Changes": "AQAAAAAAAAA=",
+ "Password": {
+ "Change Server": "ADDC",
+ "Cleartext Blob": "Erzx4o2+ZLrW+kx/dHn+s8Al9i6IYHp5mOLfa7Vi5qB/bZ3hSTyRcSxsguu3A5gE+GAP6mh7cOzDo7njgPUYdzB2qnbi5sVsMznTb3Zgz6ts8R5p+2+W97b2bL4sf445/D/rOkU5pLMAcyG+HbyH9wQ81ng8Ye13nuD+5+i6vXmivG3zqij4veVo6aeob0H6fOOUqzjpzOmHt0w3k3Nl/Efo3KrNsrAtUDpQ+sKxvPNOdqdzCzxWc1esAS8VYxI/T3jPLc11rWcr7y4uJPP0+Dali6XWrnnrZvw3LF25njI2N/7kNPiMK1gner8WaitimG5hMXKu86xWdOYB1rawshF6+Wf2rYNj7bVzNNG2QG2/L/2iLu5N4JqjDSw++39wujr+eR/2S7T/AEpuBjQ=" },
+ "DNS Domain Info": {
+ "Domain NetBios Name": "ADDOMAIN",
+ "Domain DNS Name": "addom.samba.example.com",
+ "Domain Forest Name": "addom.samba.example.com",
+ "Domain SID": "S-1-5-21-42-1337-1701",
+ "Domain GUID": "ec0ef791-e41e-44b7-8990-f05eacb06174" } }
+"""
+
+DOMINFO_BLOB_BAD_MISSING_FIELDS_TRUSTFLAGS = b"""
+ { "Reserved Flags": "AAAAAAAAAAA=",
+ "Computer Name": "LOCALADMEMBER",
+ "Account Name": "LOCALADMEMBER$",
+ "Join Time": "KgAAAAAAAAA=",
+ "Secure Channel Type": 2,
+ "Trust Type": 2,
+ "Trust Attributes": 26,
+ "Supported Encryption Types": 31,
+ "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
+ "Password Last Change": "NWUTXAAAAAA=",
+ "Password Changes": "AQAAAAAAAAA=",
+ "Password": {
+ "Change Time": "ysIkXAAAAAA=",
+ "Change Server": "ADDC",
+ "Cleartext Blob": "Erzx4o2+ZLrW+kx/dHn+s8Al9i6IYHp5mOLfa7Vi5qB/bZ3hSTyRcSxsguu3A5gE+GAP6mh7cOzDo7njgPUYdzB2qnbi5sVsMznTb3Zgz6ts8R5p+2+W97b2bL4sf445/D/rOkU5pLMAcyG+HbyH9wQ81ng8Ye13nuD+5+i6vXmivG3zqij4veVo6aeob0H6fOOUqzjpzOmHt0w3k3Nl/Efo3KrNsrAtUDpQ+sKxvPNOdqdzCzxWc1esAS8VYxI/T3jPLc11rWcr7y4uJPP0+Dali6XWrnnrZvw3LF25njI2N/7kNPiMK1gner8WaitimG5hMXKu86xWdOYB1rawshF6+Wf2rYNj7bVzNNG2QG2/L/2iLu5N4JqjDSw++39wujr+eR/2S7T/AEpuBjQ=" },
+ "DNS Domain Info": {
+ "Domain NetBios Name": "ADDOMAIN",
+ "Domain DNS Name": "addom.samba.example.com",
+ "Domain Forest Name": "addom.samba.example.com",
+ "Domain SID": "S-1-5-21-42-1337-1701",
+ "Domain GUID": "ec0ef791-e41e-44b7-8990-f05eacb06174" } }
+"""
+
+DOMINFO_BLOB_BAD_MISSING_FIELDS_DNSDOMAIN = b"""
+ { "Reserved Flags": "AAAAAAAAAAA=",
+ "Computer Name": "LOCALADMEMBER",
+ "Account Name": "LOCALADMEMBER$",
+ "Join Time": "KgAAAAAAAAA=",
+ "Secure Channel Type": 2,
+ "Trust Flags": 26,
+ "Trust Type": 2,
+ "Trust Attributes": 26,
+ "Supported Encryption Types": 31,
+ "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
+ "Password Last Change": "NWUTXAAAAAA=",
+ "Password Changes": "AQAAAAAAAAA=",
+ "Password": {
+ "Change Time": "ysIkXAAAAAA=",
+ "Change Server": "ADDC",
+ "Cleartext Blob": "Erzx4o2+ZLrW+kx/dHn+s8Al9i6IYHp5mOLfa7Vi5qB/bZ3hSTyRcSxsguu3A5gE+GAP6mh7cOzDo7njgPUYdzB2qnbi5sVsMznTb3Zgz6ts8R5p+2+W97b2bL4sf445/D/rOkU5pLMAcyG+HbyH9wQ81ng8Ye13nuD+5+i6vXmivG3zqij4veVo6aeob0H6fOOUqzjpzOmHt0w3k3Nl/Efo3KrNsrAtUDpQ+sKxvPNOdqdzCzxWc1esAS8VYxI/T3jPLc11rWcr7y4uJPP0+Dali6XWrnnrZvw3LF25njI2N/7kNPiMK1gner8WaitimG5hMXKu86xWdOYB1rawshF6+Wf2rYNj7bVzNNG2QG2/L/2iLu5N4JqjDSw++39wujr+eR/2S7T/AEpuBjQ=" },
+ "DNS Domain Info": {
+ "Domain NetBios Name": "ADDOMAIN",
+ "Domain Forest Name": "addom.samba.example.com",
+ "Domain SID": "S-1-5-21-42-1337-1701",
+ "Domain GUID": "ec0ef791-e41e-44b7-8990-f05eacb06174" } }
+"""
+
+DOMINFO_BLOB_BAD_MALFORMED_SID_1 = b"""
+ { "Reserved Flags": "AAAAAAAAAAA=",
+ "Join Time": "KgAAAAAAAAA=",
+ "Computer Name": "LOCALADMEMBER",
+ "Account Name": "LOCALADMEMBER$",
+ "Secure Channel Type": 2,
+ "Trust Flags": 26,
+ "Trust Type": 2,
+ "Trust Attributes": 26,
+ "Supported Encryption Types": 31,
+ "Salt Principal": "aG9zdC9sb2NhbGFkbWVtYmVyLmFkZG9tLnNhbWJhLmV4YW1wbGUuY29tQEFERE9NLlNBTUJBLkVYQU1QTEUuQ09N",
+ "Password Last Change": "NWUTXAAAAAA=",
+ "Password Changes": "AQAAAAAAAAA=",
+ "Password": {
+ "Change Time": "ysIkXAAAAAA=",
+ "Change Server": "ADDC",
+ "Cleartext Blob": "Erzx4o2+ZLrW+kx/dHn+s8Al9i6IYHp5mOLfa7Vi5qB/bZ3hSTyRcSxsguu3A5gE+GAP6mh7cOzDo7njgPUYdzB2qnbi5sVsMznTb3Zgz6ts8R5p+2+W97b2bL4sf445/D/rOkU5pLMAcyG+HbyH9wQ81ng8Ye13nuD+5+i6vXmivG3zqij4veVo6aeob0H6fOOUqzjpzOmHt0w3k3Nl/Efo3KrNsrAtUDpQ+sKxvPNOdqdzCzxWc1esAS8VYxI/T3jPLc11rWcr7y4uJPP0+Dali6XWrnnrZvw3LF25njI2N/7kNPiMK1gner8WaitimG5hMXKu86xWdOYB1rawshF6+Wf2rYNj7bVzNNG2QG2/L/2iLu5N4JqjDSw++39wujr+eR/2S7T/AEpuBjQ=" },
+ "DNS Domain Info": {
+ "Domain NetBios Name": "ADDOMAIN",
+ "Domain DNS Name": "addom.samba.example.com",
+ "Domain Forest Name": "addom.samba.example.com",
+ "Domain SID": "S-256-5-21-42-1337-1701",
+ "Domain GUID": "ec0ef791-e41e-44b7-8990-f05eacb06174" } }
+"""
+
+class NetPrimarytrustBaseWrapper (object):
+ """
+ Guard the base so it doesn't inherit from TestCase. This prevents it from
+ being run by unittest directly.
+ """
+
+ class NetPrimarytrustTests_Base(samba.tests.BlackboxTestCase):
+ """Blackbox tests for replaying machine account credentials."""
+ expect_success = True
+ expect_equalout = True
+ inblob = DOMINFO_BLOB_OK
+ backupfile = None
+ secrets_path = None
+
+ def setUp(self):
+ """
+ Save a backup of the secrets.tdb that the main test manipulates.
+ """
+ super(NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base, self) \
+ .setUp()
+
+ envname = os.getenv ("ENVNAME")
+ stpfx = os.getenv ("TESTENV_SELFTEST_PREFIX")
+ assert envname is not None
+ assert stpfx is not None
+ self.secrets_path = SECRETS_PATH_FMT % (stpfx, envname)
+
+ assert self.check_output("tdbbackup \"%s\"" % self.secrets_path) == b""
+ self.backupfile = "%s.bak" % self.secrets_path
+
+ def tearDown(self):
+ """
+ Move backed up secrets.tdb over the changed file.
+ """
+ super(NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base, self) \
+ .tearDown()
+ if self.backupfile is not None: # only after successful tdbbackup
+ try:
+ os.rename (self.backupfile, self.secrets_path)
+ except OSError as exn: # some test envs are weird
+ print("no [%s] during test tearDown() [err: %s]; ignoring"
+ % (self.backupfile, exn))
+
+ def test_json_readinfo (self):
+ """
+ Postconditions: the command must have terminated zero without
+ generating any messages on stdout. Also, the injection of
+ credentials must have had an effect on the ``secrets.tdb`` used
+ during the test.
+ """
+ argv_r = "%s readinfo --force" % COMMAND
+ argv_d = "%s dumpinfo --force --json" % COMMAND
+ failed = False
+ out = None
+ chksum = self.hash_file (self.secrets_path)
+
+ js1 = compat_json_loads (self.check_output (argv_d))
+
+ try:
+ out = self.check_output_with_stdin(argv_r, stdin=self.inblob,
+ timeout=CHILD_TIMEOUT)
+ except samba.tests.BlackboxProcessError as e:
+ if self.expect_success is True:
+ self.fail("Error calling [%s]: %s" % (argv_r, e))
+ failed = True
+
+ if self.expect_success is True:
+ self.assertTrue (out == b"")
+ self.assertTrue (chksum != self.hash_file (self.secrets_path))
+ injs = compat_json_loads (self.inblob)
+
+ try:
+ outjs = compat_json_loads (self.check_output (argv_d))
+ except samba.tests.BlackboxProcessError as e:
+ self.fail("Error verifying postconditions while calling "
+ "[%s]: %s" % (argv_d, e))
+
+ if self.expect_equalout is True:
+ self.assertTrue (outjs == injs)
+
+ return
+
+ if failed is False:
+ self.fail ("Expected test failure did not occur.")
+ self.assertTrue (out is None)
+
+class NetPrimarytrustOkTest \
+ (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+ pass
+
+class NetPrimarytrustBadEmptyTest \
+ (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+ expect_success = False
+ inblob = b""
+
+class NetPrimarytrustBadJunkTest \
+ (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+ """Reject non-JSON."""
+ expect_success = False
+ inblob = b"""this won't parse as json"""
+
+class NetPrimarytrustBadMalformedJSONTest \
+ (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+ """Reject incomplete JSON fragment."""
+ expect_success = False
+ inblob = b"{\"Computer Name\": \"LOCALADMEMBER"
+
+class NetPrimarytrustOkMissingTimestamps \
+ (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+ """
+ The fields ``Join Time``, ``Password Last Change``, and ``Change Time`` are
+ absent.
+ """
+ expect_success = True
+ expect_equalout = False
+ inblob = DOMINFO_BLOB_OK_MISSING_TIMESTAMPS
+
+class NetPrimarytrustOkMissingKrb5Enctypes \
+ (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+ """
+ Blob lacks ``Supported Encryption Types``; the local ones are substituted.
+ """
+ expect_success = True
+ expect_equalout = False
+ inblob = DOMINFO_BLOB_OK_MISSING_ENCTYPES
+
+class NetPrimarytrustBadMissingFields1Test \
+ (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+ """Fail on absence of field ``Trust Flags``."""
+ expect_success = False
+ inblob = DOMINFO_BLOB_BAD_MISSING_FIELDS_TRUSTFLAGS
+
+class NetPrimarytrustBadMissingFields2Test \
+ (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+ """Fail on absence of field ``DNS Domain Info->Domain DNS Name``."""
+ expect_success = False
+ inblob = DOMINFO_BLOB_BAD_MISSING_FIELDS_DNSDOMAIN
+
+class NetPrimarytrustBadMalformedSID1Test \
+ (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+ """Garbage SID version field."""
+ expect_success = False
+ inblob = DOMINFO_BLOB_BAD_MALFORMED_SID_1
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index 32887066161..3f63dc6968a 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -483,9 +483,10 @@ plantestsuite("samba4.blackbox.client_etypes_strong(ad_dc:client)", "ad_dc:clien
plantestsuite("samba4.blackbox.net_ads_dns(ad_member:local)", "ad_member:local", [os.path.join(bbdir, "test_net_ads_dns.sh"), '$DC_SERVER', '$DC_USERNAME', '$DC_PASSWORD', '$REALM', '$USERNAME', '$PASSWORD'])
plantestsuite("samba4.blackbox.samba-tool_ntacl(ad_member:local)", "ad_member:local", [os.path.join(bbdir, "test_samba-tool_ntacl.sh"), '$PREFIX'])
plantestsuite_loadlist("samba4.rpc.echo against NetBIOS alias", "ad_dc_ntvfs", [valgrindify(smbtorture4), "$LISTOPT", "$LOADLIST", 'ncacn_np:$NETBIOSALIAS', '-U$DOMAIN/$USERNAME%$PASSWORD', 'rpc.echo'])
-# json tests hook into ``chgdcpass'' to make them run in contributor CI on
-# gitlab
-planpythontestsuite("chgdcpass", "samba.tests.blackbox.netads_json")
+planpythontestsuite("ad_member:local", "samba.tests.blackbox.net_json",
+ py3_compatible=True)
+planpythontestsuite("ad_member:local", "samba.tests.blackbox.netprimarytrust",
+ py3_compatible=True)
# Tests using the "Simple" NTVFS backend
for t in ["base.rw1"]:
--
2.17.2
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <http://lists.samba.org/pipermail/samba-technical/attachments/20190110/178cbd56/signature-0001.sig>
More information about the samba-technical
mailing list