[PATCH v3] dump and restore domain trust info

Philipp Gesang philipp.gesang at intra2net.com
Thu Jan 24 14:21:49 UTC 2019


Hi,

I implemented the suggested changes to the last revisions of the
patchset [0][1].

Regarding timestamps, the existing routines to handle Generalized
Time were insufficient so I added an alternative parser and
formatter. The parser in particular (hopefully) rejects any
non-conforming input. With this, NTTIME timestamps retain the
100ns precision.

Encoding passwords as strings results in dumps that IMO are
rather ugly due to the random Unicode code points. Example:

      "Password": {
        "Change Time": "20190123141424.401118Z",
        "Change Server": "172.16.3.80",
        "Cleartext Blob": "憺㬝≠︉㨶稗朋滶Ͼ篩楿永社羚㦚斴懇ㆥ눈⽷딌砝㴜봀⺹낺ꤑ絀㛶カⷬ㺅ꖡ봦똢熳℘礕藍㡘ﯽ럤겍⳺类攴㇣搻⿶뤺珝럣Ⰲⱍ櫺㫲뗚ᄁﶚ뛳럮끏㑐꺜ꍞ更滮㑞㕘뇳Ⅴ민絸猉Ԫ랥獵ℭ㈄Ⳍ緶ꈼ빺⇊濲눶灬ꢓ뵽枘ㅵ泪㹒땎묱ㆄ⾒繉㊜놫㝦㸠⽫籽炻㪎⸵뽹ꢰ牼몟㢮똇덨ꩭ眨朱늠덝뎦뤰ꆻ。㦷멫㓹랜붞껑汛ö旓疽❨⡝ⅈ륗瓊✪" },

The terminal font I use has glyphs for just two of the codepoints
in the password string above which makes the dumps awkward to
deal with. Compare the base64 version (different password):

      "Password": {
        "Change Time": "20190123141424.401118Z",
        "Change Server": "172.16.3.80",
        "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=" },

Any opinions on this?

Due to the change in timestamp formatting, the patchset now
depends on the timespec fix I posted earlier [2]. That commit is
included so it builds when applied on top of a pristine
samba-team/devel/master.

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

Regards,
Philipp

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

Changes v2--v3:

- Serialize passwords as UTF-8 encoded strings instead of base64.
- Serialize 64 bit integers as strings instead of base64 encoded
  bytes.
- Use LDAP Generalized Time to format timestamps.
- Cosmetic issues wrt. README.Coding.
- Fix minor issues after the dumpinfo -> export rename.

Changes v1--v2

- Subcommands are named import/export instead of dumpinfo /
  readinfo; explicitly passing --json is no longer required.
- export always includes the passwords, import always accepts
  passwords.
- primarytrust import will abort if domain credentials are
  present. Passing --force overrides the check.
- Include .next_change of the info1 struct in JSON export.
- Unit test previous passwords and the contents of next_change.
- Timestamps in ISO8601 (includes a workaround for the somewhat
  aged glibc used by Gitlab CI).

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

[0] V1 https://lists.samba.org/archive/samba-technical/2019-January/131924.html
[1] V2 https://lists.samba.org/archive/samba-technical/2019-January/132023.html
[2] https://lists.samba.org/archive/samba-technical/2019-January/132066.html

-------------- next part --------------
From 4fe62110fd389e5508ab4e456b6a49949378a0d8 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Thu, 17 Jan 2019 11:06:26 +0100
Subject: [PATCH v3 01/12] lib/util: fix timespec normalization

When fixing up timespec structs, negative values for the ns part
should be taken into account. Also, the range for a valid ns part
is [0, 1000000000), not [0, 1000000000].

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 lib/util/tests/time.c | 53 +++++++++++++++++++++++++++++++++++++++++++
 lib/util/time.c       | 53 ++++++++++++++++++++++++++++++++++++-------
 lib/util/time.h       |  1 +
 3 files changed, 99 insertions(+), 8 deletions(-)

diff --git a/lib/util/tests/time.c b/lib/util/tests/time.c
index fce0eef5e2e..041816dc3f6 100644
--- a/lib/util/tests/time.c
+++ b/lib/util/tests/time.c
@@ -82,6 +82,57 @@ static bool test_timestring(struct torture_context *tctx)
 	return true;
 }
 
+static bool test_normalize_timespec(struct torture_context *tctx)
+{
+	int i;
+	const struct {
+		time_t in_s; long in_ns;
+		time_t out_s; long out_ns;
+	} data [] = {
+		  { 0, 0, 0, 0 }
+		, { 1, 0, 1, 0 }
+		, { -1, 0, -1, 0 }
+		, { 0, 1000000000, 1, 0 }
+		, { 0, 2000000000, 2, 0 }
+		, { 0, 1000000001, 1, 1 }
+		, { 0, 2000000001, 2, 1 }
+		, { 0, -1000000000, -1, 0 }
+		, { 0, -2000000000, -2, 0 }
+		, { 0, -1000000001, -2, 999999999 }
+		, { 0, -2000000001, -3, 999999999 }
+		, { 0, -1, -1, 999999999 }
+		, { 1, -1, 0, 999999999 }
+		, { -1, -1, -2, 999999999 }
+		, { 0, 999999999, 0, 999999999 }
+		, { 0, 1999999999, 1, 999999999 }
+		, { 0, 2999999999, 2, 999999999 }
+		, { 0, -999999999, -1, 1 }
+		, { 0, -1999999999, -2, 1 }
+		, { 0, -2999999999, -3, 1 }
+		, { LONG_MAX, 1000000001, LONG_MAX, 999999999 } /* overflow */
+		, { LONG_MAX,  999999999, LONG_MAX, 999999999 } /* harmless */
+		, { LONG_MAX, -1, LONG_MAX-1, 999999999 } /* -1 */
+		, { LONG_MIN, -1000000001, LONG_MIN, 0 } /* overflow */
+		, { LONG_MIN, 0, LONG_MIN, 0 } /* harmless */
+		, { LONG_MIN, 1000000000, LONG_MIN+1, 0 } /* +1 */
+	};
+
+	for (i = 0; i < sizeof(data) / sizeof(data[0]); ++i) {
+		struct timespec ts = (struct timespec)
+				   { .tv_sec  = data[i].in_s
+				   , .tv_nsec = data[i].in_ns };
+
+		normalize_timespec(&ts);
+
+		torture_assert_int_equal(tctx, ts.tv_sec, data[i].out_s,
+					 "mismatch in tv_sec");
+		torture_assert_int_equal(tctx, ts.tv_nsec, data[i].out_ns,
+					 "mismatch in tv_nsec");
+	}
+
+	return true;
+}
+
 struct torture_suite *torture_local_util_time(TALLOC_CTX *mem_ctx)
 {
 	struct torture_suite *suite = torture_suite_create(mem_ctx, "time");
@@ -92,6 +143,8 @@ struct torture_suite *torture_local_util_time(TALLOC_CTX *mem_ctx)
 								  test_http_timestring);
 	torture_suite_add_simple_test(suite, "timestring", 
 								  test_timestring);
+	torture_suite_add_simple_test(suite, "normalize_timespec",
+				      test_normalize_timespec);
 
 	return suite;
 }
diff --git a/lib/util/time.c b/lib/util/time.c
index bd067f84e8e..9dfea1798b2 100644
--- a/lib/util/time.c
+++ b/lib/util/time.c
@@ -39,6 +39,7 @@
 #endif
 
 
+#define NSEC_PER_SEC 1000000000
 
 /**
  External access to time_t_min and time_t_max.
@@ -88,10 +89,7 @@ _PUBLIC_ time_t time_mono(time_t *t)
 time_t convert_timespec_to_time_t(struct timespec ts)
 {
 	/* Ensure tv_nsec is less than 1sec. */
-	while (ts.tv_nsec > 1000000000) {
-		ts.tv_sec += 1;
-		ts.tv_nsec -= 1000000000;
-	}
+	normalize_timespec(&ts);
 
 	/* 1 ns == 1,000,000,000 - one thousand millionths of a second.
 	   increment if it's greater than 500 millionth of a second. */
@@ -950,10 +948,7 @@ void round_timespec_to_usec(struct timespec *ts)
 {
 	struct timeval tv = convert_timespec_to_timeval(*ts);
 	*ts = convert_timeval_to_timespec(tv);
-	while (ts->tv_nsec > 1000000000) {
-		ts->tv_sec += 1;
-		ts->tv_nsec -= 1000000000;
-	}
+	normalize_timespec(ts);
 }
 
 /****************************************************************************
@@ -982,3 +977,45 @@ _PUBLIC_ NTTIME unix_timespec_to_nt_time(struct timespec ts)
 
 	return d;
 }
+
+/****************************************************************************
+ Deal with nanoseconds overflow.
+****************************************************************************/
+
+void normalize_timespec(struct timespec *ts)
+{
+	lldiv_t dres;
+
+	/* most likely case: nsec is valid */
+	if ((unsigned long)ts->tv_nsec < NSEC_PER_SEC) {
+		return;
+	}
+
+	dres = lldiv(ts->tv_nsec, NSEC_PER_SEC);
+
+	/* if the operation would result in overflow, max out values and bail */
+	if (dres.quot > 0) {
+		if ((int64_t)LONG_MAX - dres.quot < ts->tv_sec) {
+			ts->tv_sec = LONG_MAX;
+			ts->tv_nsec = NSEC_PER_SEC - 1;
+			return;
+		}
+	} else {
+		if ((int64_t)LONG_MIN - dres.quot > ts->tv_sec) {
+			ts->tv_sec = LONG_MIN;
+			ts->tv_nsec = 0;
+			return;
+		}
+	}
+
+	ts->tv_nsec = dres.rem;
+	ts->tv_sec += dres.quot;
+
+	/* if the ns part was positive or a multiple of -1000000000, we're done */
+	if (ts->tv_nsec > 0 || dres.rem == 0) {
+		return;
+	}
+
+	ts->tv_nsec += NSEC_PER_SEC;
+	--ts->tv_sec;
+}
diff --git a/lib/util/time.h b/lib/util/time.h
index 1988b330576..2cfa1fa9039 100644
--- a/lib/util/time.h
+++ b/lib/util/time.h
@@ -329,5 +329,6 @@ int timespec_compare(const struct timespec *ts1, const struct timespec *ts2);
 void round_timespec_to_sec(struct timespec *ts);
 void round_timespec_to_usec(struct timespec *ts);
 NTTIME unix_timespec_to_nt_time(struct timespec ts);
+void normalize_timespec(struct timespec *ts);
 
 #endif /* _SAMBA_TIME_H_ */
-- 
2.17.2


From fda854720ec761f79e69eee2d87addb78010dc71 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Fri, 18 Jan 2019 17:10:36 +0100
Subject: [PATCH v3 02/12] lib/util: implement ldap generalized time handling

---
 lib/util/tests/time.c | 193 +++++++++++++++++++++++++++++++++++
 lib/util/time.c       | 232 ++++++++++++++++++++++++++++++++++++++++++
 lib/util/time.h       |   3 +
 3 files changed, 428 insertions(+)

diff --git a/lib/util/tests/time.c b/lib/util/tests/time.c
index 041816dc3f6..87c25f96a89 100644
--- a/lib/util/tests/time.c
+++ b/lib/util/tests/time.c
@@ -133,6 +133,197 @@ static bool test_normalize_timespec(struct torture_context *tctx)
 	return true;
 }
 
+static bool test_rfc4517_timeformat(struct torture_context *tctx)
+{
+	size_t i;
+	int bad = 0;
+#       define RFC4517_MAX_DATELEN 29 /* lib/util/time.c */
+	const char junkbuf [RFC4517_MAX_DATELEN + 1] =
+		{ 0x16, 0xa9, 0xeb, 0x61, 0xd7, 0xb6, 0x28, 0x7d, 0xa9, 0x9f
+		, 0xb9, 0xee, 0x97, 0x83, 0x14, 0xac, 0x1d, 0xff, 0x09, 0xab
+		, 0x60, 0xb8, 0x86, 0xcd, 0x62, 0xd3, 0x9f, 0xec, 0xc2, 0x00 };
+	const struct {
+		const char *const in;  /* timestamp to parse */
+		const bool valid;      /* expected to parse as valid? */
+		const char *const out; /* result of round-trip conversion */
+	} testme [] =
+		{ { "199412161032Z"            , true, "19941216103200Z" }
+		, { "199412161032.42Z"         , true, "19941216103200.42Z" }
+		, { "11111111111111.111111111Z", true, NULL }
+		, { "11111111111111,111111111Z", true, "11111111111111.111111111Z" }
+		, { "11111111111111.000001111Z", true, NULL }
+		, { "01111111111111.000001111Z", true, NULL }
+		, { "01110111111111.000001111Z", true, NULL }
+		, { "01110101111111.000001111Z", true, NULL }
+		, { "01110101011111.000001111Z", true, NULL }
+		, { "01110101010111.000001111Z", true, NULL }
+		, { "01110101010101.000001111Z", true, NULL }
+		, { "00110101010101.000001111Z", true, NULL }
+		, { "00010101010101.000001111Z", true, NULL }
+
+		/* fractional part */
+		, { "10101010101010.1Z"          , true , NULL }
+		, { "10101010101010.11Z"         , true , NULL }
+		, { "10101010101010.010Z"        , true , "10101010101010.01Z" }
+		, { "10101010101010.1Z"          , true , NULL }
+		, { "10101010101010,1Z"          , true , "10101010101010.1Z" }
+		, { "10101010101010.0Z"          , true , "10101010101010Z" }
+		, { "11111111111111.100000000Z"  , true , "11111111111111.1Z" }
+		, { "10101010101010.101010101Z"  , true , NULL }
+		, { "10101010101010.1010101010Z" , false, NULL }
+		, { "10101010101010.10101010101Z", false, NULL }
+		, { "11111111111111.999999999Z"  , true , NULL }
+		, { "11111111111111.-99999999Z"  , false, NULL }
+		, { "11111111111111.+99999999Z"  , false, NULL }
+
+		/*
+		 * optional hours and minutes: the formatter always prints
+		 * seconds and minutes, even if zero
+		 */
+		, { "000101010101.000001111Z", true, "00010101010100.000001111Z" }
+		, { "0001010101.000001111Z"  , true, "00010101010000.000001111Z" }
+		, { "10101010101010Z"        , true, NULL }
+		, { "1010101010Z"            , true, "10101010100000Z" }
+		, { "10101010101010.1Z"      , true, NULL }
+		, { "10101010101010.01Z"     , true, NULL }
+		, { "1010101010.0Z"          , true, "10101010100000Z" }
+
+		/* timezone */
+		, { "11111111111111.111111111"       , false, NULL }
+		, { "11111111111111.0"               , false, NULL }
+		, { "11111111111111"                 , false, NULL }
+		, { "1111111111Z"                    , true , "11111111110000Z" }
+		, { "111111111Z"                     , false, NULL }
+		, { "1111111111+"                    , false, NULL }
+		, { "1111111111+Z"                   , false, NULL }
+		, { "1111111111.+"                   , false, NULL }
+		, { "1111111111,+"                   , false, NULL }
+		, { "1111111111-"                    , false, NULL }
+		, { "1111111111+-"                   , false, NULL }
+		, { "1111111111+0"                   , false, NULL }
+		, { "1111111100+01"                  , true , "11111111010000Z" }
+		, { "1111111111-11"                  , true , "11111111000000Z" }
+		, { "1111111111--11"                 , false, NULL }
+		, { "1111111111+012"                 , false, NULL }
+		, { "1111111100+0123"                , true , "11111111012300Z" }
+		, { "11111111111111.111111111+Z"     , false, NULL }
+		, { "11111111111111.111111111+0100"  , true , "11111111121111.111111111Z" }
+		, { "11111111111111.111111111-0100"  , true , "11111111101111.111111111Z" }
+		, { "11111111111111.111111111+0101"  , true , "11111111121211.111111111Z" }
+		, { "11111111111111.111111111-0101"  , true , "11111111101011.111111111Z" }
+		, { "11111111111111.111111111+2400"  , false, NULL }
+		, { "11111111111111.111111111-2400"  , false, NULL }
+		, { "11111111000011.111111111+2359"  , true , "11111111235911.111111111Z" }
+		, { "11111111111111.111111111+2361"  , false, NULL }
+		, { "11111111111111.111111111-2361"  , false, NULL }
+
+		/* out of range */
+		, { "00000000000000Z"          , false, NULL }
+		, { "11111111111161.111111111Z", false, NULL }
+		, { "11111111116011.111111111Z", false, NULL }
+		, { "11111111241111.111111111Z", false, NULL }
+		, { "11111132111111.111111111Z", false, NULL }
+		, { "11111311111111.111111111Z", false, NULL }
+		, { "1111111111-1-1.111111111Z", false, NULL }
+		, { "1111111111-111.111111111Z", false, NULL }
+		, { "11111111-11111.111111111Z", false, NULL }
+		, { "-1111111111111.111111111Z", false, NULL }
+		, { "11111111111111.-11111111Z", false, NULL }
+
+		/* junk */
+		, { junkbuf, false, NULL }
+		, { "", false, NULL }
+		, { "junk", false, NULL }
+		, { "1111junk", false, NULL }
+		, { "111111junk", false, NULL }
+		, { "11111111junk", false, NULL }
+		, { "1111111111junk", false, NULL }
+		, { "111111111111junk", false, NULL }
+		};
+	const size_t ntests = sizeof(testme) / sizeof(testme[0]);
+
+	for (i = 0; i < ntests; ++i) {
+		const char *const in = testme[i].in;
+		const bool valid = testme[i].valid;
+		const char *const out = testme[i].out;
+		const char *verdict = "test incomplete";
+		bool ok;
+		char buf[255] = { 0 };
+		struct timespec ts = { 0 };
+		char *gtstamp = NULL;
+
+		if (in == NULL) {
+			break;
+		}
+
+		ok = parse_rfc4517_time (in, &ts);
+		if (!ok) {
+			if (!valid) {
+				fprintf(stderr,
+					"[%25s] → OK: expected parse failure "
+					"for invalid string\n",
+					in);
+			} else {
+				++bad;
+				fprintf(stderr,
+					"[%25s] → FAILURE: parse failure\n",
+					in);
+			}
+			continue;
+		}
+
+		if (!valid) {
+			++bad;
+			fprintf(stderr,
+				"[%25s] → FAILURE: invalid string parsed as "
+				"valid\n",
+				in);
+			continue;
+		}
+
+		ok = format_rfc4517_time (tctx, &ts, &gtstamp);
+		if (!ok) {
+			++bad;
+			fprintf(stderr, "[%25s] → FAILURE format\n", in);
+			continue;
+		}
+
+		if (out == NULL) { /* identical to input */
+			if (strcmp(in, gtstamp) == 0) {
+				verdict = "OK: output matches input, as "
+					  "expected";
+			} else {
+				++bad;
+				verdict = "FAILURE: expected output to match "
+					  "input";
+			}
+		} else {
+			if (strcmp(out, gtstamp) == 0) {
+				snprintf(buf, sizeof(buf),
+					 "OK: output matches [%s], as expected",
+					 out);
+			} else {
+				++bad;
+				snprintf(buf, sizeof(buf),
+					 "FAILURE: input does not match "
+					 "expected output [%s]", out);
+			}
+			verdict = buf;
+		}
+
+		fprintf(stderr, "[%25s] %c → %ld s, %ld ns → [%s] → %s\n",
+			in, valid ? '+' : '-',
+			ts.tv_sec, ts.tv_nsec,
+			gtstamp,
+			verdict);
+	}
+
+	torture_assert_int_equal(tctx, bad, 0,
+				 "at least one RFC4517 test failed");
+
+	return true;
+}
+
 struct torture_suite *torture_local_util_time(TALLOC_CTX *mem_ctx)
 {
 	struct torture_suite *suite = torture_suite_create(mem_ctx, "time");
@@ -145,6 +336,8 @@ struct torture_suite *torture_local_util_time(TALLOC_CTX *mem_ctx)
 								  test_timestring);
 	torture_suite_add_simple_test(suite, "normalize_timespec",
 				      test_normalize_timespec);
+	torture_suite_add_simple_test(suite, "rfc4517_timeformat",
+				      test_rfc4517_timeformat);
 
 	return suite;
 }
diff --git a/lib/util/time.c b/lib/util/time.c
index 9dfea1798b2..630dcfc12cc 100644
--- a/lib/util/time.c
+++ b/lib/util/time.c
@@ -21,6 +21,8 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include <ctype.h>
+
 #include "replace.h"
 #include "system/time.h"
 #include "byteorder.h"
@@ -1019,3 +1021,233 @@ void normalize_timespec(struct timespec *ts)
 	ts->tv_nsec += NSEC_PER_SEC;
 	--ts->tv_sec;
 }
+
+/****************************************************************************
+ read LDAP / ASN.1 Generalized Time into timespec
+****************************************************************************/
+
+#define STRUCT_TM_YEAROFFSET    1900
+#define SECONDS_PER_HOUR        3600
+#define SECONDS_PER_MINUTE        60
+#define RFC4517_MIN_DATELEN       11 /* YYYYmmddHHZ */
+#define RFC4517_MAX_DATELEN       29 /* YYYYmmddHHMMSS.fffffffff+MMSS\x00 */
+#define VALID_INT_RANGE(n, min, max) ((min) <= (n) && (n) <= (max))
+
+bool parse_rfc4517_time (const char *raw, struct timespec *ts)
+{
+	int ret;
+	size_t len;
+	int pos = 0;
+	int pos1 = 0;
+	struct timespec tstmp = { 0 };
+	struct tm tm = { 0 };
+	char buf [15] = { 0 };
+
+	if (raw == NULL) {
+		return false;
+	}
+
+	len = strlen (raw);
+	if (len < RFC4517_MIN_DATELEN) {
+		return false;
+	}
+
+	/* Step 1: parse date and time */
+
+	/*
+	 * Validate format YYYYmmddHH[MM[SS]]. The spec requires that the
+	 * date-time part be all digits but sscanf allows for signs even
+	 * with %u.
+	 */
+	(void)sscanf(raw, "%14[0-9]%n", buf, &pos1);
+	if (pos1 != 10 &&    /* YYYYmmddHH */
+	    pos1 != 12 &&    /* YYYYmmddHHMM */
+	    pos1 != 14)      /* YYYYmmddHHMMSS */
+	{
+		return false;
+	}
+
+	ret = sscanf(buf, "%4d%2d%2d%2d%n%2d%n%2d%n",
+		     &tm.tm_year,
+		     &tm.tm_mon,
+		     &tm.tm_mday,
+		     &tm.tm_hour, &pos,
+		     &tm.tm_min,  &pos,
+		     &tm.tm_sec,  &pos);
+
+	/*
+	 * Verify that we parsed as many digits as in the validation pass and
+	 * that we captured enough elements.
+	 */
+	if (pos != pos1 || !VALID_INT_RANGE(ret, 4, 6)) {
+		return false;
+	}
+
+	/* Ensure matched values are within range. */
+	if (!(VALID_INT_RANGE(tm.tm_year, 0, 9999) &&
+	      VALID_INT_RANGE(tm.tm_mon, 1, 12) &&
+	      VALID_INT_RANGE(tm.tm_mday, 0, 31) &&
+	      VALID_INT_RANGE(tm.tm_hour, 0, 23) &&
+	      VALID_INT_RANGE(tm.tm_min, 0, 59) &&
+	      VALID_INT_RANGE(tm.tm_sec, 0, 60)))
+	{
+		return false;
+	}
+
+	/* Step 2: parse fraction */
+
+	if (pos < len - 2 && (raw[pos] == '.' || raw[pos] == ',')) {
+
+		/*
+		 * At least two bytes remain and there is a decimal separator:
+		 * parse nanoseconds.
+		 */
+		int nsec = 0;
+		int mag = 9;
+		int ndigits = 0;
+
+		pos += 1;
+		while (mag > 0) {
+			const char c = raw[pos];
+
+			nsec *= 10;
+			if (isdigit(c)) {
+				const int v = c - '0';
+
+				nsec += v;
+				++pos;
+				++ndigits;
+			}
+			--mag;
+		}
+
+		while (isdigit(raw[pos])) {
+			++ndigits;
+			++pos;
+		}
+
+		/*
+		 * The spec sets no upper bound to the number of digits in the
+		 * fractional part but we can only use up to nine. In order to
+		 * prevent accidental loss of precision, additional digits
+		 * cause a parse failure.
+		 */
+		if (!VALID_INT_RANGE(ndigits, 1, 9)) {
+			return false;
+		}
+
+		tstmp.tv_nsec = nsec;
+	}
+
+	if (pos >= len) { /* The g-time-zone element must be present. */
+		return false;
+	}
+
+	/* Step 3: parse timezone: {+-}hh[mm] */
+
+	if (raw [pos] != 'Z') { /* g-differential */
+		unsigned tzh = 0;
+		unsigned tzm = 0;
+		enum { plus, minus } sign = plus;
+
+		switch (raw [pos]) {
+			default: return false;
+			case '+': sign = plus ; break;
+			case '-': sign = minus; break;
+		}
+		pos += 1;
+
+		/* Validate digits as with date-time above. */
+		pos1 = 0;
+		memset(buf, 0, sizeof(buf));
+		(void)sscanf (raw + pos, "%4[0-9]%n", buf, &pos1);
+		if (pos1 != 2 && pos1 != 4) {
+			return false;
+		}
+
+		/* Parse the differential. */
+		pos1 = 0;
+		ret = sscanf (buf, "%2d%n%2d%n",
+			      &tzh, &pos1,
+			      &tzm, &pos1);
+		if (!VALID_INT_RANGE(ret, 1, 2)) {
+			return false;
+		}
+
+		if (!VALID_INT_RANGE(tzh, 0, 23) ||
+		    !VALID_INT_RANGE(tzm, 0, 59))
+		{
+			return false;
+		}
+
+		pos += pos1;
+		tm.tm_gmtoff = tzh * SECONDS_PER_HOUR
+			     + tzm * SECONDS_PER_MINUTE;
+
+		if (sign == minus) {
+			tm.tm_gmtoff *= -1;
+		}
+	}
+
+	tm.tm_year -= STRUCT_TM_YEAROFFSET;
+	tm.tm_mon--;
+
+	errno = 0;
+	tstmp.tv_sec = tm.tm_gmtoff;
+	tstmp.tv_sec += timegm (&tm);
+	if (tstmp.tv_sec == (time_t)-1 && errno == EOVERFLOW) {
+		return false;
+	}
+
+	*ts = tstmp;
+
+	return true;
+}
+
+/****************************************************************************
+ format timespec conforming to LDAP / ASN.1 Generalized Time
+****************************************************************************/
+
+bool format_rfc4517_time (TALLOC_CTX *ctx, const struct timespec *ts,
+			  char **dst)
+{
+	size_t pos;
+	char *ret = NULL;
+	struct tm tm = { 0 };
+
+	if (dst == NULL) {
+		return false;
+	}
+
+	if (gmtime_r(&ts->tv_sec, &tm) == NULL) {
+		return false;
+	}
+
+	ret = talloc_zero_size(ctx, RFC4517_MAX_DATELEN);
+	if (ret == NULL) {
+		return false;
+	}
+
+	pos = snprintf(ret, RFC4517_MAX_DATELEN, "%04u%02u%02u%02u%02u%02u",
+		       (unsigned)tm.tm_year + STRUCT_TM_YEAROFFSET,
+		       (unsigned)tm.tm_mon + 1,
+		       (unsigned)tm.tm_mday,
+		       (unsigned)tm.tm_hour,
+		       (unsigned)tm.tm_min,
+		       (unsigned)tm.tm_sec);
+
+	if (ts->tv_nsec > 0) {
+		pos += snprintf(ret + pos, RFC4517_MAX_DATELEN - pos, ".%09lu",
+				(unsigned long)ts->tv_nsec);
+		while (ret[pos-1] == '0') { /* trim trailing zeros */
+			--pos;
+		}
+	}
+
+	ret[pos] = 'Z';
+	ret[pos+1] = '\x00';
+
+	*dst = ret;
+
+	return true;
+}
diff --git a/lib/util/time.h b/lib/util/time.h
index 2cfa1fa9039..106ada76fab 100644
--- a/lib/util/time.h
+++ b/lib/util/time.h
@@ -330,5 +330,8 @@ void round_timespec_to_sec(struct timespec *ts);
 void round_timespec_to_usec(struct timespec *ts);
 NTTIME unix_timespec_to_nt_time(struct timespec ts);
 void normalize_timespec(struct timespec *ts);
+bool parse_rfc4517_time (const char *raw, struct timespec *ts);
+bool format_rfc4517_time (TALLOC_CTX *ctx, const struct timespec *ts,
+			  char **dst);
 
 #endif /* _SAMBA_TIME_H_ */
-- 
2.17.2


From dd281b00897ad9c5c0e8741a09bb2ad2ba42f3d5 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 8 Oct 2018 14:59:50 +0200
Subject: [PATCH v3 03/12] 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 a32594de1adbb2995878a5f037039d81e827748f Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 1 Oct 2018 11:24:56 +0200
Subject: [PATCH v3 04/12] 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_timespec(),
        - json_add_ntstatus(),

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

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 | 862 +++++++++++++++++++++++++++++-
 lib/audit_logging/audit_logging.h |  41 ++
 2 files changed, 900 insertions(+), 3 deletions(-)

diff --git a/lib/audit_logging/audit_logging.c b/lib/audit_logging/audit_logging.c
index fe2df2c9f8a..d82eb96a08e 100644
--- a/lib/audit_logging/audit_logging.c
+++ b/lib/audit_logging/audit_logging.c
@@ -24,6 +24,7 @@
 
 #include "includes.h"
 
+#include "inttypes.h"
 #include "librpc/ndr/libndr.h"
 #include "lib/tsocket/tsocket.h"
 #include "libcli/security/dom_sid.h"
@@ -84,7 +85,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 +97,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 +389,33 @@ 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,310 @@ 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 strings.
+ *
+ * @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)
+{
+	char buf [21] = { 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;
+	}
+
+	snprintf(buf, sizeof(buf), "%"PRIu64, value);
+
+	return json_add_string(object, name, buf);
+}
+
+/*
+ * @brief Extract 64 bit unsigned integer from JSON object.
+ *
+ * Read a string value named 'name' from the json object and scan it as
+ * uint64_t.
+ *
+ * @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;
+	const char *stru64 = NULL;
+	uint64_t u64tmp = 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_string(ctx, object, name, &stru64);
+	if (ret != 0 || stru64 == NULL) {
+		DBG_ERR("Unable to read field [%s] as string data\n", name);
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+
+	ret = sscanf(stru64, "%"PRIu64, &u64tmp);
+	if (ret != 1) {
+		DBG_ERR("Invalid decoded string field [%s]: content "
+			"[%s] does not scan as uint64_t",
+			name, stru64);
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+	TALLOC_FREE(ctx);
+
+	*value = u64tmp;
+
+	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.
+ *
+ * The timestamp is stored as UTC in RFC4517 Generalized Time with
+ * nanoseconds precision.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param ts pointer to the value
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ *
+ */
+int json_add_timespec(struct json_object *object,
+		      const char *name,
+		      const struct timespec *ts)
+{
+	int ret;
+	TALLOC_CTX *ctx = NULL;
+	char *tstamp = NULL;
+	struct timespec tstmp = *ts;
+
+	if (json_is_invalid(object)) {
+		DBG_ERR("Unable to add timespec field [%s], "
+			"target object is invalid\n",
+			name);
+		return JSON_ERROR;
+	}
+
+	ctx = talloc_new(NULL);
+	if (ctx == NULL) {
+		DBG_ERR("Out of memory creating temporary context\n");
+		return JSON_ERROR;
+	}
+
+	normalize_timespec(&tstmp);
+
+	if (!format_rfc4517_time(ctx, ts, &tstamp)) {
+		DBG_ERR("Unable to add timespec field [%s], "
+			"failed to format timestamp\n",
+			name);
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+
+	ret = json_add_string(object, name, tstamp);
+	TALLOC_FREE(ctx);
+
+	return ret;
+}
+
 /*
  * @brief Add a boolean value to a JSON object.
  *
@@ -495,6 +874,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.
  *
@@ -907,7 +1358,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 +1404,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 +1537,357 @@ 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 timespec from a JSON object.
+ *
+ * Retrieve a timestamp in RFC4517 Generalized Time from a JSON object key and
+ * return it as struct timespec.
+ *
+ * @param object The JSON object to access.
+ * @param name Key.
+ * @param ts Pointer to the destination object.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ */
+int json_get_timespec(const struct json_object *object,
+		      const char *name,
+		      struct timespec *ts)
+{
+	int ret;
+	bool ok;
+	TALLOC_CTX *ctx = NULL;
+	const char *raw = NULL;
+	struct timespec tstmp = { 0 };
+
+	if (ts == NULL) {
+		DBG_ERR("Unable to store time_t value [%s]: "
+			"target location is NULL\n",
+			name);
+		return JSON_ERROR;
+	}
+
+	ctx = talloc_new(NULL);
+	if (ctx == NULL) {
+		DBG_ERR("Out of memory creating temporary context\n");
+		return JSON_ERROR;
+	}
+
+	ret = json_get_string(ctx, object, name, &raw);
+	if (ret != 0 || raw == NULL) {
+		DBG_ERR("Failed to get timestamp value from JSON object\n");
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+
+	ok = parse_rfc4517_time(raw, &tstmp);
+	if (!ok) {
+		DBG_ERR("Failed to parse [%s] as Generalized Time\n", raw);
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+
+	TALLOC_FREE(ctx);
+	*ts = tstmp;
+
+	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;
+	bool ok;
+	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;
+	}
+
+	ok = dom_sid_parse(data, &tmp);
+	if (!ok) {
+		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 Add an NT status code.
+ *
+ * Status codes are converted to their symbolic names.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param nt_code the status code to add
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ *
+ */
+int json_add_ntstatus(struct json_object *object, const char *name,
+		      const NTSTATUS nt_code)
+{
+	const char *str = NULL;
+
+	if (object == NULL) {
+		DBG_ERR("Invalid argument, refusing to operate on NULL"
+			"object\n");
+		return JSON_ERROR;
+	}
+
+	if (json_is_invalid(object)) {
+		DBG_ERR("Unable to add NT status field [%s] with value "
+			"[%"PRIu32"], target object is invalid\n",
+			name, NT_STATUS_V(nt_code));
+		return JSON_ERROR;
+	}
+
+	str = nt_errstr(nt_code);
+	if (str == NULL) {
+		DBG_ERR("Failed to look up name of NT status code [%"PRIu32"]"
+			"for value [%s]\n", NT_STATUS_V(nt_code), name);
+		return JSON_ERROR;
+	}
+
+	return json_add_string(object, name, str);
+}
+
+/*
+ * @brief Get an NT status code from object.
+ *
+ * Retrieve status code contained in a string member of the object.
+ *
+ * @param object The JSON object to access.
+ * @param name Field name to access.
+ * @param nt_code Pointer to the destination object.
+ *
+ * @return 0 the operation was successful
+ *        -1 the operation failed
+ */
+int json_get_ntstatus(const struct json_object *object,
+		      const char *name,
+		      NTSTATUS *nt_code)
+{
+	int ret = 0;
+	const char *data = NULL;
+	TALLOC_CTX *ctx = NULL;
+
+	/* object and name are checked in json_get_string() */
+	if (nt_code == NULL) {
+		DBG_ERR("Invalid argument, refusing to operate on NULL "
+			"target\n");
+		return JSON_ERROR;
+	}
+
+	ctx = talloc_new(NULL);
+	if (ctx == NULL) {
+		DBG_ERR("Out of memory creating temporary context\n");
+		return JSON_ERROR;
+	}
+
+	ret = json_get_string(ctx, object, name, &data);
+	if (ret != 0 || data == NULL) {
+		DBG_ERR("Failed to get string value [%s] from JSON object "
+			"for conversion to NT status", name);
+		TALLOC_FREE(ctx);
+		return JSON_ERROR;
+	}
+
+	/*
+	 * The conversion does not signal failure; unknown codes end up as
+	 * NT_STATUS_UNSUCCESSFUL.
+	 */
+        *nt_code = nt_status_string_to_code(data);;
+	TALLOC_FREE(ctx);
+
+	return 0;
+}
+
 #endif
diff --git a/lib/audit_logging/audit_logging.h b/lib/audit_logging/audit_logging.h
index 86e9134a86a..6d12f84da67 100644
--- a/lib/audit_logging/audit_logging.h
+++ b/lib/audit_logging/audit_logging.h
@@ -52,6 +52,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 +60,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_timespec(struct json_object *object,
+					   const char *name,
+					   const struct timespec *ts);
 _WARN_UNUSED_RESULT_ int json_add_bool(struct json_object *object,
 				       const char *name,
 				       const bool value);
@@ -86,6 +96,33 @@ _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_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_timespec(const struct json_object *object,
+					   const char *name,
+					   struct timespec *ts);
+_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_add_ntstatus(struct json_object *object,
+					   const char *name,
+					   const NTSTATUS nt_code);
+_WARN_UNUSED_RESULT_ int json_get_ntstatus(const struct json_object *object,
+					   const char *name, NTSTATUS *nt_code);
+
+_WARN_UNUSED_RESULT_ bool json_has_key(const struct json_object *object,
+				       const char *name);
 
 _WARN_UNUSED_RESULT_ struct json_object json_get_array(
     struct json_object *object, const char *name);
@@ -93,5 +130,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 e82939b07ebcd81573610007b2ec75707a3ab251 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Thu, 4 Oct 2018 09:44:33 +0200
Subject: [PATCH v3 05/12] 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_timespec(), json_get_timespec(),
    - json_add_ntstatus(), and json_get_ntstatus().

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

diff --git a/lib/audit_logging/tests/audit_logging_test.c b/lib/audit_logging/tests/audit_logging_test.c
index acd2a4f697f..192dbcfdbc9 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,378 @@ 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;
+	const char *strtmp = NULL;
+	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 string type of entries created to guard against
+	 * accidental encoding as json numbers
+	 */
+	for (i = 0; i < TEST_JSON_UINT64_NVALUES; ++i) {
+		rc = json_get_string(ctx, &object, testme_u64[i].k, &strtmp);
+		assert_int_equal(0, rc);
+	}
+
+	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_timespec(void **state)
+{
+	struct json_object object;
+	int rc = 0;
+	struct timespec ts = { 0 };
+
+	object = json_new_object();
+	assert_true(object.valid);
+
+	rc = json_add_timespec(&object, "t_unix", &ts);
+	assert_int_equal(0, rc);
+	assert_int_equal(1, json_object_size(object.root));
+
+	ts.tv_sec = time(NULL);
+	rc = json_add_timespec(&object, "t_now", &ts);
+	assert_int_equal(0, rc);
+	assert_int_equal(2, json_object_size(object.root));
+
+	/* EOVERFLOW */
+	ts.tv_sec = LONG_LONG_MAX;
+	rc = json_add_timespec(&object, "t_heat_death", &ts);
+	assert_int_equal(JSON_ERROR, rc);
+	assert_int_equal(2, json_object_size(object.root));
+
+	json_free(&object);
+}
+
+static void test_json_get_timespec(void **state)
+{
+	struct json_object object;
+	int rc = 0;
+	struct timespec ts = { 0 };
+	const struct timespec t_now = { time(NULL), 0 };
+
+	object = json_new_object();
+	assert_true(object.valid);
+
+	rc = json_add_timespec(&object, "t_unix", &ts);
+	assert_int_equal(0, rc);
+	assert_int_equal(1, json_object_size(object.root));
+
+	rc = json_add_timespec(&object, "t_now", &t_now);
+	assert_int_equal(0, rc);
+	assert_int_equal(2, json_object_size(object.root));
+
+	/* EOVERFLOW */
+	ts.tv_sec = LONG_LONG_MAX;
+	rc = json_add_timespec(&object, "t_heat_death", &ts);
+	assert_int_equal(JSON_ERROR, rc);
+	assert_int_equal(2, json_object_size(object.root));
+
+	/* EOVERFLOW */
+	ts.tv_sec = LONG_LONG_MIN;
+	rc = json_add_timespec(&object, "t_big_bang", &ts);
+	assert_int_equal(JSON_ERROR, rc);
+	assert_int_equal(2, json_object_size(object.root));
+
+	rc = json_get_timespec(&object, "t_unix", &ts);
+	assert_int_equal(0, rc);
+	assert_int_equal(ts.tv_sec,  0);
+	assert_int_equal(ts.tv_nsec, 0);
+
+	rc = json_get_timespec(&object, "t_now", &ts);
+	assert_int_equal(0, rc);
+	assert_int_equal(ts.tv_sec,  t_now.tv_sec);
+	assert_int_equal(ts.tv_nsec, t_now.tv_nsec);
+
+	rc = json_get_timespec(&object, "t_heat_death", &ts);
+	assert_int_equal(JSON_ERROR, rc);
+
+	json_free(&object);
+}
+
+static void test_json_get_timespec_strings(void **state)
+{
+	struct json_object object;
+	int rc;
+	struct timespec ts;
+	const char *testdata =
+		"{\"ok1\": \"11111111111111Z\""
+		",\"ok2\": \"33330303033333+0300\""
+		",\"ok3\": \"19700101000000+0000\""
+		",\"ok4\": \"19700101000000+0042\""
+		",\"ok5\": \"19700101000000.42Z\""
+		",\"ok6\": \"19700101000000.000000042Z\""
+		",\"ok7\": \"19700101000000.1+0100\""
+		",\"bad1\": 42"
+		",\"bad2\": \"\""
+		",\"bad3\": \"11114211T111111Z\""
+		",\"bad4\": \"11111142T111111Z\""
+		",\"bad5\": \"11111111\""
+		",\"bad6\": \"1111-11-11 11:11:11Z\""
+		",\"bad7\": \"1111-11-11T11:11:11Z\""
+		"}";
+
+	object = json_new_null();
+
+	rc = json_from_string(&object, testdata);
+	assert_int_equal(0, rc);
+	assert_false(json_is_invalid(&object));
+
+	rc = json_get_timespec(&object, "ok1", &ts);
+	assert_int_equal(0, rc);
+
+	rc = json_get_timespec(&object, "ok2", &ts);
+	assert_int_equal(0, rc);
+	assert_int_equal(ts.tv_sec, 43017460413);
+
+	rc = json_get_timespec(&object, "ok3", &ts);
+	assert_int_equal(0, rc);
+	assert_int_equal(ts.tv_sec, 0);
+
+	rc = json_get_timespec(&object, "ok4", &ts);
+	assert_int_equal(0, rc);
+	assert_int_equal(ts.tv_sec, 42 * 60);
+
+	rc = json_get_timespec(&object, "ok5", &ts);
+	assert_int_equal(0, rc);
+	assert_int_equal(ts.tv_sec, 0);
+	assert_int_equal(ts.tv_nsec, 420000000);
+
+	rc = json_get_timespec(&object, "ok6", &ts);
+	assert_int_equal(0, rc);
+	assert_int_equal(ts.tv_sec, 0);
+	assert_int_equal(ts.tv_nsec, 42);
+
+	rc = json_get_timespec(&object, "ok7", &ts);
+	assert_int_equal(0, rc);
+	assert_int_equal(ts.tv_sec, 1 * 60 * 60);
+	assert_int_equal(ts.tv_nsec, 100000000);
+
+	rc = json_get_timespec(&object, "bad1", &ts);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_timespec(&object, "bad2", &ts);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_timespec(&object, "bad3", &ts);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_timespec(&object, "bad4", &ts);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_timespec(&object, "bad5", &ts);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_timespec(&object, "bad6", &ts);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_timespec(&object, "bad7", &ts);
+	assert_int_equal(JSON_ERROR, rc);
+
+	json_free(&object);
+}
+
+static void test_json_add_ntstatus(void **state)
+{
+	struct json_object object;
+	int rc = 0;
+
+	object = json_new_object();
+	assert_true(object.valid);
+
+	rc = json_add_ntstatus(NULL, "yikes", NT_STATUS_OK);
+	assert_int_equal(JSON_ERROR, rc);
+	assert_int_equal(0, json_object_size(object.root));
+
+	rc = json_add_ntstatus(&object, "ok", NT_STATUS_OK);
+	assert_int_equal(0, rc);
+	assert_int_equal(1, json_object_size(object.root));
+
+	rc = json_add_ntstatus(&object, "ok", NT_STATUS(UINT32_MAX));
+	assert_int_equal(0, rc);
+	assert_int_equal(1, json_object_size(object.root));
+
+	json_free(&object);
+}
+
+static void test_json_get_ntstatus(void **state)
+{
+	struct json_object object;
+	int rc;
+	NTSTATUS status = NT_STATUS_OK;
+	const char *testdata =
+		"{\"ok1\": \"NT_STATUS_OK\""
+		",\"ok2\": \"NT_STATUS_NO_MEMORY\""
+		",\"bad1\": 42"
+		",\"bad2\": \"NT_STATUS_NOPROBLEMO\""
+		"}";
+
+	object = json_new_null();
+
+	rc = json_from_string(&object, testdata);
+	assert_int_equal(0, rc);
+	assert_false(json_is_invalid(&object));
+
+	rc = json_get_ntstatus(&object, "enoent", &status);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_ntstatus(NULL, "ok1", &status);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_ntstatus(&object, "ok1", &status);
+	assert_int_equal(0, rc);
+	assert_true(NT_STATUS_IS_OK(status));
+
+	rc = json_get_ntstatus(&object, "ok2", &status);
+	assert_int_equal(0, rc);
+	assert_true(NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY));
+
+	rc = json_get_ntstatus(&object, "bad1", &status);
+	assert_int_equal(JSON_ERROR, rc);
+
+	rc = json_get_ntstatus(&object, "bad2", &status);
+	assert_int_equal(0, rc);
+	assert_true(NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL));
+
+	json_free(&object);
+}
+
 static void test_json_add_bool(void **state)
 {
 	struct json_object object;
@@ -173,6 +551,63 @@ static void test_json_add_string(void **state)
 	assert_int_equal(JSON_ERROR, rc);
 }
 
+static void test_json_get_string(void **state)
+{
+	int ret;
+	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();
+
+	ret = json_from_string(&object, testdata);
+	assert_int_equal(0, ret);
+	assert_non_null(object.root);
+	assert_true(json_is_object(object.root));
+
+	ret = json_get_string(ctx, &object, "test_string", &res);
+	assert_int_equal(0, ret);
+	assert_non_null(res);
+	assert_string_equal(res, "ook");
+
+	res = NULL;
+	ret = json_get_string(ctx, &object, "test_int", &res);
+	assert_int_equal(JSON_ERROR, ret);
+	assert_null(res);
+
+	ret = json_get_string(ctx, &object, "test_null", &res);
+	assert_int_equal(JSON_ERROR, ret);
+	assert_null(res);
+
+	ret = json_get_string(ctx, &object, "enoent", &res);
+	assert_int_equal(JSON_ERROR, ret);
+	assert_null(res);
+
+	res = "notnull";
+	ret = json_get_string(ctx, &object, NULL, &res);
+	assert_int_equal(JSON_ERROR, ret);
+	assert_string_equal(res, "notnull");
+
+	ret = json_get_string(ctx, NULL, "test_string", &res);
+	assert_int_equal(JSON_ERROR, ret);
+	assert_string_equal(res, "notnull");
+
+	ret = json_get_string(ctx, &object, "test_string", NULL);
+	assert_int_equal(JSON_ERROR, ret);
+
+	json_free(&object);
+
+	ret = json_get_string(ctx, &object, "test_string", &res);
+	assert_int_equal(JSON_ERROR, ret);
+	assert_string_equal(res, "notnull");
+
+	TALLOC_FREE(ctx);
+}
+
 static void test_json_add_object(void **state)
 {
 	struct json_object object;
@@ -560,6 +995,46 @@ static void test_json_add_sid(void **state)
 	assert_int_equal(JSON_ERROR, rc);
 }
 
+static void test_json_get_sid(void **state)
+{
+	int ret;
+	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();
+
+	ret = json_from_string(&object, testdata);
+	assert_int_equal(0, ret);
+	assert_non_null(object.root);
+	assert_true(json_is_object(object.root));
+
+	ret = json_get_sid(&object, "null", &sid);
+	assert_int_equal(0, ret);
+	assert_true(dom_sid_equal(&sid, &global_sid_NULL));
+
+	ret = json_get_sid(&object, "allauth", &sid);
+	assert_int_equal(0, ret);
+	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));
+
+	ret = json_get_sid(&object, "enoent", &sid);
+	assert_int_equal(JSON_ERROR, ret);
+
+	ret = json_get_sid(&object, "null", NULL);
+	assert_int_equal(JSON_ERROR, ret);
+
+	ret = json_get_sid(&object, NULL, &sid);
+	assert_int_equal(JSON_ERROR, ret);
+
+	json_free(&object);
+}
+
 static void test_json_add_guid(void **state)
 {
 	struct json_object object;
@@ -600,6 +1075,30 @@ static void test_json_add_guid(void **state)
 	assert_int_equal(JSON_ERROR, rc);
 }
 
+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_to_string(void **state)
 {
 	struct json_object object;
@@ -634,6 +1133,166 @@ 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;
+	ssize_t sz;
+	struct json_object object;
+	FILE *fh = NULL;
+	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 */
+
+	/* dup(2) to preserve the fd through fclose() */
+	fh = fdopen (dup (fd), "r");
+	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 +1492,18 @@ int main(int argc, const char **argv)
 {
 	const struct CMUnitTest tests[] = {
 		cmocka_unit_test(test_json_add_int),
+		cmocka_unit_test(test_json_add_uint32),
+		cmocka_unit_test(test_json_get_uint32),
+		cmocka_unit_test(test_json_add_uint64),
+		cmocka_unit_test(test_json_get_uint64),
+		cmocka_unit_test(test_json_add_timespec),
+		cmocka_unit_test(test_json_get_timespec),
+		cmocka_unit_test(test_json_get_timespec_strings),
+		cmocka_unit_test(test_json_add_ntstatus),
+		cmocka_unit_test(test_json_get_ntstatus),
 		cmocka_unit_test(test_json_add_bool),
 		cmocka_unit_test(test_json_add_string),
+		cmocka_unit_test(test_json_get_string),
 		cmocka_unit_test(test_json_add_object),
 		cmocka_unit_test(test_json_add_to_array),
 		cmocka_unit_test(test_json_add_timestamp),
@@ -842,10 +1511,14 @@ 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_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 6d6e2e83514912f6862b10e61a21de512eb60572 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Fri, 14 Dec 2018 10:16:18 +0100
Subject: [PATCH v3 06/12] s3: net: add primarytrust subcommand `export'

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

Naturally this functionality requires Samba to be built with
libjansson.

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

diff --git a/source3/include/secrets.h b/source3/include/secrets.h
index 24ae5bd0664..14a3c58824b 100644
--- a/source3/include/secrets.h
+++ b/source3/include/secrets.h
@@ -119,6 +119,8 @@ void secrets_debug_domain_info(int lvl, const struct secrets_domain_info1 *info,
 			       const char *name);
 char *secrets_domain_info_string(TALLOC_CTX *mem_ctx, const struct secrets_domain_info1 *info1,
 				 const char *name, bool include_secrets);
+char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
+			       const struct secrets_domain_info1 *info1);
 NTSTATUS secrets_fetch_or_upgrade_domain_info(const char *domain,
 					TALLOC_CTX *mem_ctx,
 					struct secrets_domain_info1 **pinfo);
diff --git a/source3/passdb/machine_account_secrets.c b/source3/passdb/machine_account_secrets.c
index dfc21f295a1..4a2a434f1d2 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,326 @@ 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
+			(TALLOC_CTX *mem_ctx,
+			 struct json_object *jsdst,
+			 const char *key,
+			 const struct secrets_domain_info1_password *pw)
+{
+	int ret;
+	struct timespec tstmp;
+	struct json_object jsobj = json_new_object();
+	DATA_BLOB password_utf8 = { 0 };
+
+	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;
+	}
+
+	tstmp = nt_time_to_unix_timespec(pw->change_time);
+
+	ret = json_add_timespec(&jsobj, "Change Time", &tstmp);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Change Server", pw->change_server);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	if (pw->cleartext_blob.data != NULL) {
+		const bool ok = convert_string_talloc(mem_ctx,
+						      CH_UTF16MUNGED, CH_UTF8,
+						      pw->cleartext_blob.data,
+						      pw->cleartext_blob.length,
+						      &password_utf8.data,
+						      &password_utf8.length);
+		if (!ok) {
+			DBG_ERR("password: conversion utf-16 -> "
+				"utf-8 failed\n");
+			goto failure;
+		}
+
+		ret = json_add_stringn(&jsobj, "Cleartext Blob",
+				       (const char*)password_utf8.data,
+				       password_utf8.length);
+		if (ret != 0) {
+			goto failure;
+		}
+	}
+
+	ret = json_add_object(jsdst, key, &jsobj);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	return true;
+failure:
+	json_free(&jsobj);
+	return false;
+}
+
+static bool json_add_secrets_domain_info1_change
+			(TALLOC_CTX *mem_ctx,
+			 struct json_object *jsdst,
+			 const char *key,
+			 const struct secrets_domain_info1_change *next)
+{
+	int ret;
+	struct timespec tstmp = { 0 };
+	struct json_object jsobj = json_new_object();
+
+	if (json_is_invalid(&jsobj)) {
+		DBG_ERR("refusing to write to invalid JSON object\n");
+
+		return false;
+	}
+
+	if (json_is_invalid(jsdst)) {
+		DBG_ERR("refusing to write to invalid JSON object\n");
+
+		goto failure;
+	}
+
+	if (key == NULL) {
+		DBG_ERR("refusing to access NULL key\n");
+
+		goto failure;
+	}
+
+	ret = json_add_ntstatus(&jsobj, "Local Status", next->local_status);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_ntstatus(&jsobj, "Remote Status", next->remote_status);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	tstmp = nt_time_to_unix_timespec (next->change_time);
+
+	ret = json_add_timespec(&jsobj, "Change Time", &tstmp);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Change Server", next->change_server);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	if (next->password != NULL &&
+	    !json_add_secrets_domain_info1_password(mem_ctx,
+						    &jsobj,
+						    "Password",
+						    next->password))
+	{
+		goto failure;
+	}
+
+	ret = json_add_object(jsdst, key, &jsobj);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	return true;
+failure:
+	json_free(&jsobj);
+	return false;
+}
+
+char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
+			       const struct secrets_domain_info1 *info1)
+{
+	struct timespec	    tstmp  = { 0 };
+	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;
+	}
+
+	ret = json_add_uint64(&jsinfo, "Reserved Flags", info1->reserved_flags);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	tstmp = nt_time_to_unix_timespec(info1->join_time);
+
+	ret = json_add_timespec(&jsinfo, "Join Time", &tstmp);
+	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;
+	}
+
+	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> */
+
+	ret = json_add_uint32(&jsinfo, "Trust Flags", info1->trust_flags);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_int(&jsinfo, "Trust Type", (int)info1->trust_type);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_uint32(&jsinfo, "Trust Attributes", info1->trust_flags);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	/* NOTE: reserved_routing is unused, cf. secrets.idl */
+	ret = json_add_uint32(&jsinfo, "Supported Encryption Types",
+			      info1->supported_enc_types);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsinfo, "Salt Principal", info1->salt_principal);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	tstmp = nt_time_to_unix_timespec(info1->password_last_change);
+
+	ret = json_add_timespec(&jsinfo, "Password Last Change", &tstmp);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_uint64(&jsinfo, "Password Changes",
+			      info1->password_changes);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	if (info1->next_change != NULL &&
+	    !json_add_secrets_domain_info1_change (mem_ctx,
+						   &jsinfo,
+						   "Next Change ",
+						   info1->next_change))
+	{
+		goto failure;
+	}
+
+	if (info1->password != NULL &&
+	    !json_add_secrets_domain_info1_password (mem_ctx,
+						     &jsinfo,
+						     "Password",
+						     info1->password))
+	{
+		goto failure;
+	}
+
+	if (info1->old_password != NULL &&
+	    !json_add_secrets_domain_info1_password (mem_ctx,
+						     &jsinfo,
+						     "Old Password",
+						     info1->old_password))
+	{
+		goto failure;
+	}
+
+	if (info1->older_password != NULL &&
+	    !json_add_secrets_domain_info1_password (mem_ctx,
+						     &jsinfo,
+						     "Older Password",
+						     info1->older_password))
+	{
+		goto failure;
+	}
+
+	ret = json_add_object(&jsinfo, "DNS Domain Info", &jsdns);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	result = json_to_string(mem_ctx, &jsinfo);
+	json_free(&jsinfo);
+
+	return result;
+failure:
+	json_free(&jsdns);
+	json_free(&jsinfo);
+
+	return NULL;
+}
+#else /* [HAVE_JANSSON] */
+char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
+			       const struct secrets_domain_info1 *info1)
+{
+	DBG_ERR("JSON support not available\n");
+
+	return NULL;
+}
+#endif /* [HAVE_JANSSON] */
+
 static NTSTATUS secrets_store_domain_info1_by_key(const char *key,
 					const struct secrets_domain_info1 *info1)
 {
diff --git a/source3/utils/net.c b/source3/utils/net.c
index 8350e8c0967..15f960b07e9 100644
--- a/source3/utils/net.c
+++ b/source3/utils/net.c
@@ -56,6 +56,10 @@
 #include "utils/net_afs.h"
 #endif
 
+#ifdef HAVE_JANSSON
+#include "audit_logging.h"
+#endif /* [HAVE_JANSSON] */
+
 /***********************************************************************/
 /* end of internationalization section                                 */
 /***********************************************************************/
@@ -94,6 +98,54 @@ static void set_line_buffering(FILE *f)
 	setvbuf(f, NULL, _IOLBF, 0);
 }
 
+#ifdef HAVE_JANSSON
+static int net_primarytrust_export(struct net_context *c, int argc,
+				   const char **argv)
+{
+	int role = lp_server_role();
+	const char *domain = lp_workgroup();
+	struct secrets_domain_info1 *info = NULL;
+	char *str = NULL;
+	NTSTATUS status;
+
+	if (role >= ROLE_ACTIVE_DIRECTORY_DC) {
+		d_printf(_("net primarytrust export is only supported "
+			 "on a DOMAIN_MEMBER for now.\n"));
+		return 1;
+	}
+
+	status = secrets_fetch_or_upgrade_domain_info(domain,
+						      talloc_tos(),
+						      &info);
+	if (!NT_STATUS_IS_OK(status)) {
+		d_fprintf(stderr,
+			  _("Unable to fetch the information for domain[%s] "
+			  "in the secrets database.\n"),
+			  domain);
+		return 1;
+	}
+
+	str = secrets_domain_info_json(talloc_tos(), info);
+	if (str == NULL) {
+		d_fprintf(stderr, "secrets_domain_info_json() failed.\n");
+		return 1;
+	}
+
+	d_printf("%s", str);
+
+	TALLOC_FREE(info);
+	return 0;
+}
+#else /* [HAVE_JANSSON] */
+static int net_primarytrust_export(struct net_context *_c, int _argc,
+				   const char **_argv)
+{
+	d_fprintf(stderr, _("JSON support not available\n"));
+
+	return -1;
+}
+#endif /* [HAVE_JANSSON] */
+
 static int net_primarytrust_dumpinfo(struct net_context *c, int argc,
 				     const char **argv)
 {
@@ -165,6 +217,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.")
 		},
+		{
+			"export",
+			net_primarytrust_export,
+			NET_TRANSPORT_LOCAL,
+			N_("Export the workstation trust"),
+			N_("  net [options] primarytrust export'\n"
+			   "    Export the workstation trust in secrets.tdb "
+			   "as JSON.\n"
+			   "    Requires the -f flag to include the password "
+			   "values.")
+		},
 		{NULL, NULL, 0, NULL, NULL}
 	};
 
-- 
2.17.2


From f70c9e7695c047bb0693351cd65b19f75098deb0 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Fri, 14 Dec 2018 17:01:56 +0100
Subject: [PATCH v3 07/12] s3: net: add primarytrust subcommand `import`

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

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

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

diff --git a/source3/include/secrets.h b/source3/include/secrets.h
index 14a3c58824b..7dcf9778833 100644
--- a/source3/include/secrets.h
+++ b/source3/include/secrets.h
@@ -124,6 +124,11 @@ char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
 NTSTATUS secrets_fetch_or_upgrade_domain_info(const char *domain,
 					TALLOC_CTX *mem_ctx,
 					struct secrets_domain_info1 **pinfo);
+struct json_object;
+NTSTATUS secrets_set_domain_info_json(const char *domain,
+				      TALLOC_CTX *mem_ctx,
+				      const struct json_object *jsdata,
+				      bool overwrite);
 NTSTATUS secrets_prepare_password_change(const char *domain, const char *dcname,
 					 const char *cleartext_unix,
 					 TALLOC_CTX *mem_ctx,
diff --git a/source3/passdb/machine_account_secrets.c b/source3/passdb/machine_account_secrets.c
index 4a2a434f1d2..15038dbf24d 100644
--- a/source3/passdb/machine_account_secrets.c
+++ b/source3/passdb/machine_account_secrets.c
@@ -963,6 +963,190 @@ 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;
+	bool ok;
+	struct secrets_domain_info1_password *pw;
+	struct json_object jspw;
+	struct timespec tstmp = { 0 };
+	DATA_BLOB password_utf8 = { 0 };
+
+	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_timespec(&jspw, "Change Time", &tstmp);
+	if (ret != 0) {
+		DBG_DEBUG("password: Optional field \"Change Time\" missing or "
+			  "invalid; falling back on current time.\n");
+		tstmp.tv_sec = time(NULL);
+	}
+
+	pw->change_time = unix_timespec_to_nt_time(tstmp);
+
+	ret = json_get_string(ctx, &jspw, "Change Server", &pw->change_server);
+	if (ret != 0) {
+		DBG_ERR("password: Field \"Change Server\" missing or "
+			"invalid.\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_string(ctx,
+			      &jspw,
+			      "Cleartext Blob",
+			      (const char **)&password_utf8.data);
+	if (ret != 0) {
+		DBG_ERR("password: Field \"Cleartext Blob\" missing or "
+			"invalid.\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+	password_utf8.length = strlen((const char*)password_utf8.data);
+
+	ok = convert_string_talloc(ctx,
+				   CH_UTF8, CH_UTF16MUNGED,
+				   password_utf8.data,
+				   password_utf8.length,
+				   &pw->cleartext_blob.data,
+				   &pw->cleartext_blob.length);
+	if (!ok) {
+		DBG_ERR("password: Failed to convert \"Cleartext Blob\" "
+			"(%zu B) to UTF-18LE\n",
+			password_utf8.length);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	*dst = pw;
+
+	return NT_STATUS_OK;
+}
+
+static NTSTATUS json_get_secrets_domain_info1_change
+			(TALLOC_CTX *ctx,
+			 const struct json_object *jssrc,
+			 const char *key,
+			 struct secrets_domain_info1_change **dst)
+{
+	int ret;
+	struct secrets_domain_info1_change *next = NULL;
+	struct json_object jsnext;
+	NTSTATUS status;
+	struct timespec tstmp;
+
+	if (json_is_invalid(jssrc)) {
+		DBG_ERR("next change: refusing to read from invalid JSON "
+			"object\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	if (key == NULL) {
+		DBG_ERR("next change: refusing to access NULL key\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	if (dst == NULL) {
+		DBG_ERR("next change: refusing to write to NULL address\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	next = talloc_zero_size(ctx, sizeof(*next));
+	if (next == NULL) {
+		DBG_ERR("next change: error allocating memory for domain "
+			"password\n");
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	jsnext = json_get_object(discard_const_p(struct json_object, jssrc),
+				 key);
+	if (json_is_invalid(&jsnext)) {
+		DBG_ERR("next change: failed to retrieve field [%s] from "
+			"domain info\n", key);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_ntstatus(&jsnext, "Local Status", &next->local_status);
+	if (ret != 0) {
+		DBG_DEBUG("next change: Field \"Local Status\" missing or "
+			  "invalid.");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_ntstatus(&jsnext, "Remote Status", &next->remote_status);
+	if (ret != 0) {
+		DBG_DEBUG("next change: Field \"Remote Status\" missing or "
+			  "invalid.");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	tstmp = (struct timespec){ 0, 0 };
+	ret = json_get_timespec(&jsnext, "Change Time", &tstmp);
+	if (ret != 0) {
+		DBG_DEBUG("next change: Field \"Change Time\" missing or "
+			  "invalid.");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	next->change_time = unix_timespec_to_nt_time(tstmp);
+
+	ret = json_get_string(ctx,
+			      &jsnext,
+			      "Change Server",
+			      &next->change_server);
+	if (ret != 0) {
+		DBG_ERR("next change: Field \"Change Server\" missing or "
+			"invalid.\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	if (json_has_key (&jsnext, "Password")) {
+		status = json_get_secrets_domain_info1_password
+							(ctx,
+							 &jsnext,
+							 "Password",
+							 &next->password);
+		if (!NT_STATUS_IS_OK(status)) {
+			DBG_ERR("next change: Optional field \"Password\" "
+				"invalid\n");
+			return status;;
+		}
+	}
+
+	*dst = next;
+
+	return NT_STATUS_OK;
+}
+
 char *secrets_domain_info_json(TALLOC_CTX *mem_ctx,
 			       const struct secrets_domain_info1 *info1)
 {
@@ -1621,6 +1805,349 @@ static NTSTATUS secrets_domain_info_password_create(TALLOC_CTX *mem_ctx,
 	return NT_STATUS_OK;
 }
 
+#ifdef HAVE_JANSSON
+static NTSTATUS
+secrets_json_get_domain_info (TALLOC_CTX *mem_ctx,
+			      const struct json_object *jsdata,
+			      struct secrets_domain_info1 **dst)
+{
+	int ret;
+	NTSTATUS status;
+	struct secrets_domain_info1 *info1 = NULL;
+	struct timespec tstmp;
+	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;
+	}
+
+	tstmp = (struct timespec){ 0, 0 };
+	ret = json_get_timespec(jsdata, "Join Time", &tstmp);
+
+	if (ret != 0) {
+		DBG_DEBUG("Optional field \"Join Time\" missing or "
+			  "invalid; falling back on current time.\n");
+		tstmp.tv_sec = time(NULL);
+	}
+
+	info1->join_time = unix_timespec_to_nt_time(tstmp);
+
+	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_string(mem_ctx, jsdata, "Salt Principal",
+				      &info1->salt_principal);
+
+		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;
+		}
+
+		info1->salt_principal = talloc_strdup(mem_ctx, nacl);
+		if (info1->salt_principal == NULL) {
+			DBG_ERR("Out of memory allocating %zu B for default "
+				"salt.\n",
+				strlen (nacl));
+			return NT_STATUS_NO_MEMORY;
+		}
+	}
+
+	tstmp = (struct timespec){ 0, 0 };
+	ret = json_get_timespec(jsdata, "Password Last Change", &tstmp);
+	if (ret != 0) {
+		DBG_DEBUG("Optional field \"Password Last Change\" missing or "
+			  "invalid; falling back on current time.\n");
+		tstmp.tv_sec = time(NULL);
+	}
+
+	info1->password_last_change = unix_timespec_to_nt_time(tstmp);
+
+	ret = json_get_uint64(jsdata,
+			      "Password Changes",
+			      &info1->password_changes);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Password Changes\" missing or "
+			"invalid.\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	if (json_has_key (jsdata, "Next Change")) {
+		status = json_get_secrets_domain_info1_change
+						(mem_ctx,
+						 jsdata,
+						 "Next Change",
+						 &info1->next_change);
+		if (!NT_STATUS_IS_OK(status)) {
+			DBG_ERR("Optional field \"Next Change\" invalid.\n");
+			return status;
+		}
+	}
+
+	if (json_has_key (jsdata, "Password")) {
+		status = json_get_secrets_domain_info1_password
+						(mem_ctx,
+						 jsdata,
+						 "Password",
+						 &info1->password);
+		if (!NT_STATUS_IS_OK(status)) {
+			DBG_ERR("Optional field \"Password\" invalid\n");
+			return status;
+		}
+	}
+
+	if (json_has_key (jsdata, "Old Password")) {
+		status = json_get_secrets_domain_info1_password
+						(mem_ctx,
+						 jsdata,
+						 "Old Password",
+						 &info1->old_password);
+		if (!NT_STATUS_IS_OK(status)) {
+			DBG_ERR("Optional field \"Old Password\" invalid\n");
+			return status;
+		}
+	}
+
+	if (json_has_key (jsdata, "Older Password")) {
+		status = json_get_secrets_domain_info1_password
+						(mem_ctx,
+						 jsdata,
+						 "Older Password",
+						 &info1->older_password);
+		if (!NT_STATUS_IS_OK(status)) {
+			DBG_ERR("Optional field \"Older Password\" invalid\n");
+			return status;
+		}
+	}
+
+	jsdns = json_get_object(discard_const_p(struct json_object, jsdata),
+				"DNS Domain Info");
+	if (json_is_invalid(&jsdns)) {
+		DBG_ERR("Mandatory field \"DNS Domain Info\" missing or "
+			"invalid.\n");
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_string(mem_ctx,
+			      &jsdns,
+			      "Domain NetBios Name",
+			      &info1->domain_info.name.string);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Domain NetBios Name\" missing or "
+			"invalid.\n");
+		json_free(&jsdns);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_string(mem_ctx,
+			      &jsdns,
+			      "Domain DNS Name",
+			      &info1->domain_info.dns_domain.string);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Domain DNS Name\" missing or "
+			"invalid.\n");
+		json_free(&jsdns);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_string(mem_ctx,
+			      &jsdns,
+			      "Domain Forest Name",
+			      &info1->domain_info.dns_forest.string);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Domain Forest Name\" missing or "
+			"invalid.\n");
+		json_free(&jsdns);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	info1->domain_info.sid = talloc_zero_size
+					(mem_ctx,
+					 sizeof(*info1->domain_info.sid));
+	if (info1->domain_info.sid == NULL) {
+		DBG_ERR("Out of memory allocating storage for domain SID\n");
+		json_free(&jsdns);
+		return NT_STATUS_NO_MEMORY;
+	}
+
+	ret = json_get_sid(&jsdns, "Domain SID", info1->domain_info.sid);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Domain SID\" missing or "
+			"invalid.\n");
+		json_free(&jsdns);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	ret = json_get_guid(&jsdns,
+			    "Domain GUID",
+			    &info1->domain_info.domain_guid);
+	if (ret != 0) {
+		DBG_ERR("Mandatory field \"Domain GUID\" missing or "
+			"invalid.\n");
+		json_free(&jsdns);
+		return NT_STATUS_INVALID_PARAMETER;
+	}
+
+	*dst = info1;
+
+	return NT_STATUS_OK;
+}
+#else /* [HAVE_JANSSON] */
+static NTSTATUS
+secrets_json_get_domain_info (TALLOC_CTX *mem_ctx,
+			      const struct json_object *jsdata,
+			      struct secrets_domain_info1 **dst)
+{
+	DBG_ERR("JSON support not available\n");
+
+	return NT_STATUS_NOT_SUPPORTED;
+}
+#endif /* [HAVE_JANSSON] */
+
+NTSTATUS secrets_set_domain_info_json(const char *domain,
+				      TALLOC_CTX *mem_ctx,
+				      const struct json_object *jsdata,
+				      bool overwrite)
+{
+	int ret;
+	NTSTATUS status;
+	struct secrets_domain_info1 *info1 = NULL;
+	struct db_context *db = NULL;
+
+	if (!overwrite) {
+		status = secrets_fetch_domain_info(domain, mem_ctx, &info1);
+		if (info1 != NULL) {
+			TALLOC_FREE(info1);
+			info1 = NULL;
+		}
+		if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+			DBG_ERR("Primary trust for domain %s exists; "
+				"use --force to overwrite.\n",
+				domain);
+			return NT_STATUS_INVALID_PARAMETER;
+		}
+	}
+
+	status = secrets_json_get_domain_info (mem_ctx, jsdata, &info1);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_json_get_domain_info() failed\n");
+		return status;
+	}
+
+	db = secrets_db_ctx();
+	ret = dbwrap_transaction_start(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+			domain);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	secrets_debug_domain_info(DBGLVL_INFO, info1, "set");
+
+	status = secrets_store_domain_info(info1, true /* upgrade */);
+	if (!NT_STATUS_IS_OK(status)) {
+		DBG_ERR("secrets_store_domain_info() failed "
+			"for %s - %s\n", domain, nt_errstr(status));
+		dbwrap_transaction_cancel(db);
+		return status;
+	}
+
+	ret = dbwrap_transaction_commit(db);
+	if (ret != 0) {
+		DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+			domain);
+		dbwrap_transaction_cancel(db);
+		return NT_STATUS_INTERNAL_DB_ERROR;
+	}
+
+	return status;
+}
+
 NTSTATUS secrets_fetch_or_upgrade_domain_info(const char *domain,
 					TALLOC_CTX *mem_ctx,
 					struct secrets_domain_info1 **pinfo)
diff --git a/source3/utils/net.c b/source3/utils/net.c
index 15f960b07e9..425a48b9e3d 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"
@@ -136,6 +137,47 @@ static int net_primarytrust_export(struct net_context *c, int argc,
 	TALLOC_FREE(info);
 	return 0;
 }
+
+static int net_primarytrust_import(struct net_context *c, int argc,
+				   const char **argv)
+{
+	int ret = 0;
+	NTSTATUS status = NT_STATUS_OK;
+	int role = lp_server_role();
+	struct json_object jsobj = json_new_object ();
+	const char *domain = lp_workgroup();
+	bool overwrite = c->opt_force;
+
+	if (role >= ROLE_ACTIVE_DIRECTORY_DC) {
+		d_printf(_("net primarytrust import is only supported "
+			 "on a DOMAIN_MEMBER for now.\n"));
+		return 1;
+	}
+
+	if (isatty(STDIN_FILENO) == 1) {
+		set_line_buffering(stderr);
+		/* signal user that net expects some input */
+		d_fprintf(stderr, "hint: waiting for input\n");
+	}
+
+	ret = json_from_FILEp(&jsobj, stdin);
+	if (ret != 0 || json_is_invalid(&jsobj)) {
+		d_fprintf(stderr, _("Error reading JSON from stdin.\n"));
+		return 1;
+	}
+
+	status = secrets_set_domain_info_json(domain, talloc_tos(), &jsobj,
+					      overwrite);
+	json_free (&jsobj);
+
+	if (!NT_STATUS_IS_OK(status)) {
+		d_fprintf(stderr,
+			  _("Failed to inject the provided domain info.\n"));
+		return 1;
+	}
+
+	return 0;
+}
 #else /* [HAVE_JANSSON] */
 static int net_primarytrust_export(struct net_context *_c, int _argc,
 				   const char **_argv)
@@ -144,6 +186,14 @@ static int net_primarytrust_export(struct net_context *_c, int _argc,
 
 	return -1;
 }
+
+static int net_primarytrust_import(struct net_context *_c, int _argc,
+				   const char **_argv)
+{
+	d_fprintf(stderr, _("JSON support not available\n"));
+
+	return -1;
+}
 #endif /* [HAVE_JANSSON] */
 
 static int net_primarytrust_dumpinfo(struct net_context *c, int argc,
@@ -228,6 +278,16 @@ static int net_primarytrust(struct net_context *c, int argc, const char **argv)
 			   "    Requires the -f flag to include the password "
 			   "values.")
 		},
+		{
+			"import",
+			net_primarytrust_import,
+			NET_TRANSPORT_LOCAL,
+			N_("Import the workstation trust"),
+			N_("  net [options] primarytrust import'\n"
+			   "    Import the workstation trust from JSON dump.\n"
+			   "    The -f flag is required to overwrite existing "
+			   "values.")
+		},
 		{NULL, NULL, 0, NULL, NULL}
 	};
 
-- 
2.17.2


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

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

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

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

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


From dfb8da3d841e20c65d224b4acd98ee635c98a747 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 8 Oct 2018 14:00:35 +0200
Subject: [PATCH v3 09/12] testprogs: blackbox test `net primarytrust
 {ex,im}port'

Test wellformedness of --json output and whether `net
primarytrust import' accepts it back as-is.

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

diff --git a/testprogs/blackbox/test_net_ads.sh b/testprogs/blackbox/test_net_ads.sh
index d3c4de5b741..9fa8d6fd4c2 100755
--- a/testprogs/blackbox/test_net_ads.sh
+++ b/testprogs/blackbox/test_net_ads.sh
@@ -60,6 +60,15 @@ 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 export" $net_tool primarytrust export || failed=`expr $failed + 1`
+
+# Dump domainfo once and attempt to reimport it; the import must fail without
+# --force on account of the info being already present.
+dominfo="$($net_tool primarytrust export 2>/dev/null)"
+testit_expect_failure "primarytrust import(json):reject" $VALGRIND $net_tool primarytrust import <<<"${dominfo}" || failed=`expr $failed + 1`
+testit "primarytrust import(json):force" $VALGRIND $net_tool primarytrust import --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 5bad0689fa8809f5b0ed60ffd7a1308e59fabd87 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Thu, 11 Oct 2018 11:06:25 +0200
Subject: [PATCH v3 10/12] python/samba/tests: add stdin handling helper when
 testing commands

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

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

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

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


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

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

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

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


From 0a9e8d7ac9f7a9c7f44c0b845977bf1f57ea4d15 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Fri, 5 Oct 2018 11:00:09 +0200
Subject: [PATCH v3 12/12] tests/blackbox: cover `net primarytrust {ex,im}port
 in tests'

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

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

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

diff --git a/python/samba/tests/blackbox/netads_json.py b/python/samba/tests/blackbox/net_json.py
similarity index 53%
rename from python/samba/tests/blackbox/netads_json.py
rename to python/samba/tests/blackbox/net_json.py
index 1c254468d36..d8531490312 100644
--- a/python/samba/tests/blackbox/netads_json.py
+++ b/python/samba/tests/blackbox/net_json.py
@@ -1,4 +1,4 @@
-# Blackbox tests for the "net ads ... --json" commands
+# Blackbox tests for the "net ... --json" commands
 # Copyright (C) 2018 Intra2net AG
 #
 # This program is free software; you can redistribute it and/or modify
@@ -21,9 +21,17 @@ import re
 import samba.tests
 from samba.compat import get_string
 
-COMMAND         = "bin/net ads"
+COMMAND_NET_ADS      = "bin/net ads"
+COMMAND_NET_PTRUST   = "bin/net primarytrust"
 # extract keys from non-json version
-PLAIN_KEY_REGEX = re.compile ("^([^ \t:][^:]*):")
+PLAIN_KEY_REGEX      = re.compile ("^([^ \t:][^:]*):")
+
+
+def compat_json_loads (raw):
+    # in some python versions (<=3.4?) json.loads() is picky about the input
+    if isinstance (raw, bytes):
+        raw = raw.decode ("ascii", "ignore")
+    return json.loads (raw)
 
 class BaseWrapper (object):
     """
@@ -31,19 +39,20 @@ class BaseWrapper (object):
     being run by unittest directly.
     """
 
-    class NetAdsJSONTests_Base(samba.tests.BlackboxTestCase):
+    class NetJSONTests_Base(samba.tests.BlackboxTestCase):
         """Blackbox tests for JSON output of the net ads suite of commands."""
-        subcmd = None
+        command = "/bin/false"
+        subcmd  = None
 
         def setUp(self):
-            super(BaseWrapper.NetAdsJSONTests_Base, self).setUp()
+            super(BaseWrapper.NetJSONTests_Base, self).setUp()
 
         def test_json_wellformed (self):
             """The output of ``--json`` commands must parse as JSON."""
-            argv = "%s %s --json" % (COMMAND, self.subcmd)
+            argv = "%s %s --json" % (self.command, self.subcmd)
             try:
-                out = self.check_output(argv)
-                json.loads (get_string(out))
+                out = self.check_output_with_stdin(argv, stdin=None)
+                compat_json_loads (get_string (out))
             except samba.tests.BlackboxProcessError as e:
                 self.fail("Error calling [%s]: %s" % (argv, e))
 
@@ -53,32 +62,45 @@ class BaseWrapper (object):
             respective plain counterpart.
 
             Does not check nested dictionaries (e. g. the ``Flags`` value of
-            ``net ads lookup``..
+            ``net ads lookup``..)
             """
-            argv = "%s %s" % (COMMAND, self.subcmd)
+            if self.matching is False: self.skipTest ("test does not apply")
+
+            argv = "%s %s" % (self.command, self.subcmd)
             try:
-                out_plain = get_string(self.check_output(argv))
+                out_plain = get_string(self.check_output_with_stdin(argv,
+                                                                    stdin=None))
             except samba.tests.BlackboxProcessError as e:
                 self.fail("Error calling [%s]: %s" % (argv, e))
 
-            argv = "%s %s --json" % (COMMAND, self.subcmd)
+            argv = "%s %s --json" % (self.command, self.subcmd)
             try:
-                out_jsobj = self.check_output(argv)
+                out_jsobj = get_string(self.check_output_with_stdin(argv,
+                                                                    stdin=None))
             except samba.tests.BlackboxProcessError as e:
                 self.fail("Error calling [%s]: %s" % (argv, e))
 
-            parsed = json.loads (get_string(out_jsobj))
+            parsed = compat_json_loads (get_string (out_jsobj))
 
             for key in [ re.match (PLAIN_KEY_REGEX, line).group(1)
                          for line in out_plain.split ("\n")
-                            if line != "" and line [0] not in " \t:" ]:
+                         if line != "" and line [0] not in " \t:" ]:
                 self.assertTrue (parsed.get (key) is not None)
                 del parsed [key]
 
             self.assertTrue (len (parsed) == 0) # tolerate no leftovers
 
-class NetAdsJSONInfoTests(BaseWrapper.NetAdsJSONTests_Base):
-    subcmd = "info"
+class NetJSON_AdsInfoTests(BaseWrapper.NetJSONTests_Base):
+    command  = COMMAND_NET_ADS
+    subcmd   = "info"
+    matching = True
 
-class NetAdsJSONlookupTests(BaseWrapper.NetAdsJSONTests_Base):
-    subcmd = "lookup"
+class NetJSON_AdsLookupTests(BaseWrapper.NetJSONTests_Base):
+    command  = COMMAND_NET_ADS
+    subcmd   = "lookup"
+    matching = True
+
+class NetJSON_PrimarytrustExportTests(BaseWrapper.NetJSONTests_Base):
+    command  = COMMAND_NET_PTRUST # does not actually need --json
+    subcmd   = "export"
+    matching = False
diff --git a/python/samba/tests/blackbox/netprimarytrust.py b/python/samba/tests/blackbox/netprimarytrust.py
new file mode 100644
index 00000000000..cfa41c32bd2
--- /dev/null
+++ b/python/samba/tests/blackbox/netprimarytrust.py
@@ -0,0 +1,525 @@
+# 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 binascii
+import json
+import os
+
+import samba.tests
+from samba.samba3 import param as s3param
+from samba.compat import get_string
+
+COMMAND              = "bin/net primarytrust"
+CHILD_TIMEOUT        = 3 # s, don't delay tests expecting input
+SECRETS_PATH_FMT     = "%s/secrets.tdb"
+
+def compat_json_loads (raw):
+    # in some python versions (<=3.4?) json.loads() is picky about the input
+    if isinstance (raw, bytes):
+        raw = raw.decode ("utf-8", "ignore")
+    return json.loads (raw)
+
+TESTPW_1 = b"eea096eaa886eeb39ce79093efbfadee958deeb08be79383e6ab85eb858ce69" \
+           b"f94eab38deeb7bbebab80e6ba87eb9c92c994e2a5a4eaa3b2ef9298e2b3a8ea" \
+           b"a6b4ef8ba2e397b8e2ba86ea9db5e69c93e38bace2aeb1e7a4bae6ad9ceea5a" \
+           b"aea98a7e3b7a8e7b2b0ef89b6eb9db3e6a192e38e9be6819aef9c93eb9c8aeb" \
+           b"a58ae78782e3b985e6bea9e3aa9bee8eb6e684b1ef8db8e3b8bbe3bbbae69e9" \
+           b"fe38f9feb9889ef82b9e7afbeea8980ea86a3ea8c97e2b8a5ea82aee38182e6" \
+           b"8494e6acb5e7b7bbe6b1a6ea94baef96ace7bb80ef9fbee3948fea868cee91b" \
+           b"0dc90e2a890efa2a5ea93bae392afe684baebb6b1eabcaeefa9abefafb0e380" \
+           b"a0eab297ee9c91e2b098e29187eb848ce78a8ee6aa90e68f8ae79093e299b9e" \
+           b"b84b5ee8f8aeb9fa8eab5a5ea90bce693a2e39aabe2a8bae79a93ea8dbde38e" \
+           b"bfe2a59def99afeaa7bde69c84e3a792ee9d9ceb8cbce78cb5d097e6bc86e29" \
+           b"5b9e3b5b8e786a5eb88b7eab1a8eabe86e284a0e295a6e384a6eb9db6e6ada7" \
+           b"ef8cb2eb96a9efb395ebafbae6ad88e6aba8efaaa9efb39aef88b0e28ba1e3a" \
+           b"4a8eab0a7e3abbaefab8feb8f8cebb8ace2b1bceeacb4efa192ef9fbcef8db9" \
+           b"e2b49be2a8baef92a7e38880e28dbcee84afe690bae2acb1ee8184e2be90e3b" \
+           b"98feb8db8eab89ceb8a82e39faae7beb7eba5b7e696bbe398bcee829fefbd89" \
+           b"e6b0ade2b79ae6aa86eeae89"
+
+TESTPW_2 = b"eaafb2e7a1bbe2b888eaaaabe2b4b8ef978beaa4bbeb8d9deead88e28e9aeba" \
+           b"d9cef9188e3be8be3ada9ef8399e3928aef92a8c3abeebda0e68885ebb490e6" \
+           b"9ab7e2b4a1ee8e9beaa2b4e2ae90c79aeb8ca2e7a086e7ae88ef90ace2a89de" \
+           b"aa3bce6a49feb87a4e79a99eb89a4e68285efa28deb97b6ef8599efb08be7b8" \
+           b"adee9494e7a7b0ee9a80eeab8be7aaa0efb387e28baeefb5b9eab785ea89b8e" \
+           b"3a482ebb683ee8ea5efb0bde786beef9086ef968ae6b0b3eba999ef879ed286" \
+           b"e7aab6eb9dafe3b8a9ef8db9e68483e7b3afefb8abeab3b5e7ba81ea948feba" \
+           b"8b9e3b0abef858ce39ea3e7899ee6bdb6efb6b6ee9e92e68787e2989be7b486" \
+           b"e6b784e3a898ee9884efbc96ea84ade3a684ee83b2eb82aee7acbaefa7abe39" \
+           b"9a9ea80abe682bbea9897e6af99ee9781e2be8fe6bd9cef8c84ea96a5ebac97" \
+           b"e28ea7eeb39be7af97e2b6b5ebb6bbea8481ea9a80e2898ae6bd8ceeb3bee79" \
+           b"29eee8382e683a4e78e90e7b9b5e3afb7ebbcb5eebf9befb6a9eb9281e6938b" \
+           b"d884e7b6bae698a0e28483e6a3b9e3b19dea879be79dbae78c81e29083e6a1b" \
+           b"beebfa5e38e81ebadb0e28a8ce7a3bbe6bd8eeaaba8eea98be685bce79ba7e7" \
+           b"b9aae7bf94ef9d80ea91a5ea8bbfe2a193ee9c91e2bbaeeeb1a9dfa8eb8085e" \
+           b"69081ea9083e3aa80eb8e81e6bdbde69c9ee3a2b8e68285e796b6e788a2e383" \
+           b"8ae79b88e28098"
+
+TESTPW_3 = b"e686bae3ac9de289a0efb889e3a8b6e7a897e69c8bef8f83eeadb1e6bbb6cfb" \
+           b"eee9385e7afa9e6a5bfe6b0b8efa98ceea8a5e7be9aef9795e3a69ae696b4e6" \
+           b"8787eebca9e386a5eb8888e2bdb7eb948ce7a09de3b49cebb480e2bab9eb82b" \
+           b"aeaa491eead9be7b580e39bb6efbdb6e2b7acee8588e3ba85ef9586ea96a1eb" \
+           b"b4a6eb98a2e786b3e28498e7a495eeaf9cefa4a3eeb4b7e3a198efafbdeb9fa" \
+           b"4eab28de2b3baef85a5efaaaee694b4ee8f87e387a3ee8891e690bbe2bfb6ef" \
+           b"98abeba4bae78f9deb9fa3e2b082ee9f84e2b18de6abbae3abb2ef8c8feb979" \
+           b"aefbea2efb69aeb9bb3eb9faeee9ab8eb818fe39190eaba9cea8d9ee69bb4e6" \
+           b"bbaeee81a2e3919ee39598eb87b3ef8bb5e285a4ebafbce7b5b8eea782e78c8" \
+           b"9ee8abfd4aaeb9ea5efa6a7e284ade38884e2b38ce7b7b6ea88bcebb9bae287" \
+           b"8ae6bfb2eb88b6eea3a3e781acef9badeaa293ef9dacef9e98efa1a5eeb283e" \
+           b"eb590ebb5bde69e98e385b5e6b3aae3b992eeadafeb958eebacb1e38684e2be" \
+           b"92e7b989eeb29be38a9ceb86abe39da6e3b8a0e2bdabeeb396e7b1bde782bbe" \
+           b"3aa8ee2b8b5ebbdb9eaa2b0e789bcebaa9fe3a2aeeb9887ee9fbceb8da8eaa9" \
+           b"ade79ca8e69cb1eb8aa0eb8d9deb8ea6eba4b0ea86bbee99b4efbda1e3a6b7e" \
+           b"ea89eefa09aeea599eba9abe393b9eb9e9ceea685ebb69eeea79aeabb91eebe" \
+           b"9be6b19beea1b5c3b6ef9287e69793e796bde29da8e2a19de28588eba597e79" \
+           b"38ae29caaef82b1eeb1b2"
+
+# The Gitlab CI containers forces a round-trip encoding so we can use string
+# interpolation which Python 3.4 doesn't support for bytes.
+DOMINFO_BLOB_OK = """
+    { "Reserved Flags": "0",
+      "Join Time": "20111111111111Z",
+      "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": "host/localadmember.addom.samba.example.com at ADDOM.SAMBA.EXAMPLE.COM",
+      "Password Last Change": "20181111133742Z",
+      "Password Changes": "0",
+      "Password": {
+        "Change Time": "20190123102110.789051Z",
+        "Change Server": "gwt-server-2008.gwt.local",
+        "Cleartext Blob": "%s" },
+      "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" } }
+""" % binascii.unhexlify (TESTPW_1).decode ()
+
+DOMINFO_BLOB_OK_NEXT_CHANGE = """
+    { "Reserved Flags": "0",
+      "Join Time": "20111111111111Z",
+      "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": "host/localadmember.addom.samba.example.com at ADDOM.SAMBA.EXAMPLE.COM",
+      "Password Last Change": "20181111133742Z",
+      "Password Changes": "1",
+      "Next Change ": {
+        "Local Status": "NT_STATUS_OK",
+        "Remote Status": "NT_STATUS_NOT_COMMITTED",
+        "Change Time": "20191111111111Z",
+        "Change Server": "ADDC",
+        "Password": {
+          "Change Time": "20191111111111Z",
+          "Change Server": "ADDC",
+          "Cleartext Blob": "%s" } },
+      "Password": {
+        "Change Time": "20181111111111Z",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "%s" },
+      "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" } }
+""" % (binascii.unhexlify (TESTPW_1).decode ()
+      ,binascii.unhexlify (TESTPW_2).decode ())
+
+DOMINFO_BLOB_OK_MULTIPASS = """
+    { "Reserved Flags": "0",
+      "Join Time": "20180808080808.0808Z",
+      "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": "host/localadmember.addom.samba.example.com at ADDOM.SAMBA.EXAMPLE.COM",
+      "Password Last Change": "20121212121212Z",
+      "Password Changes": "3",
+      "Password": {
+        "Change Time": "20111111111111.1111Z",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "%s" },
+      "Old Password": {
+        "Change Time": "20101010101010.101Z",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "%s" },
+      "Older Password": {
+        "Change Time": "20090909090909.0909Z",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "%s" },
+      "DNS Domain Info": {
+        "Domain NetBios Name": "ADDOMAIN",
+        "Domain DNS Name": "addom.samba.example.com",
+        "Domain Forest Name": "addom.samba.example.com",
+        "Domain SID": "S-1-5-21-3-5-8-13-21-34-55-89",
+        "Domain GUID": "00000000-1111-2222-3333-444444444444" } }
+""" % (binascii.unhexlify (TESTPW_1).decode ()
+      ,binascii.unhexlify (TESTPW_2).decode ()
+      ,binascii.unhexlify (TESTPW_3).decode ())
+
+DOMINFO_BLOB_OK_MISSING_ENCTYPES = """
+    { "Reserved Flags": "0",
+      "Join Time": "20111111111111Z",
+      "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": "host/localadmember.addom.samba.example.com at ADDOM.SAMBA.EXAMPLE.COM",
+      "Password Last Change": "20181111133742Z",
+      "Password Changes": "1",
+      "Password": {
+        "Change Time": "20181111111111Z",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "%s" },
+      "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" } }
+""" % binascii.unhexlify (TESTPW_1).decode ()
+
+DOMINFO_BLOB_OK_MISSING_TIMESTAMPS = """
+    { "Reserved Flags": "0",
+      "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": "host/localadmember.addom.samba.example.com at ADDOM.SAMBA.EXAMPLE.COM",
+      "Password Changes": "1",
+      "Password": {
+        "Change Server": "ADDC",
+        "Cleartext Blob": "%s" },
+      "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" } }
+""" % binascii.unhexlify (TESTPW_2).decode ()
+
+DOMINFO_BLOB_BAD_MISSING_FIELDS_TRUSTFLAGS = """
+    { "Reserved Flags": "0",
+      "Computer Name": "LOCALADMEMBER",
+      "Account Name": "LOCALADMEMBER$",
+      "Join Time": "20111111111111Z",
+      "Secure Channel Type": 2,
+      "Trust Type": 2,
+      "Trust Attributes": 26,
+      "Supported Encryption Types": 31,
+      "Salt Principal": "host/localadmember.addom.samba.example.com at ADDOM.SAMBA.EXAMPLE.COM",
+      "Password Last Change": "20181111133742Z",
+      "Password Changes": "1",
+      "Password": {
+        "Change Time": "20181111111111Z",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "%s" },
+      "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" } }
+""" % binascii.unhexlify (TESTPW_3).decode ()
+
+DOMINFO_BLOB_BAD_INCOMPLETE_PASSWORD = """
+    { "Reserved Flags": "0",
+      "Join Time": "20111111111111Z",
+      "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": "host/localadmember.addom.samba.example.com at ADDOM.SAMBA.EXAMPLE.COM",
+      "Password Last Change": "20181111133742Z",
+      "Password Changes": "1",
+      "Password": {
+        "Change Time": "20181111111111Z",
+        "Cleartext Blob": "%s" },
+      "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" } }
+""" % binascii.unhexlify (TESTPW_1).decode ()
+
+DOMINFO_BLOB_BAD_MISSING_FIELDS_DNSDOMAIN = """
+    { "Reserved Flags": "0",
+      "Computer Name": "LOCALADMEMBER",
+      "Account Name": "LOCALADMEMBER$",
+      "Join Time": "20111111111111Z",
+      "Secure Channel Type": 2,
+      "Trust Flags": 26,
+      "Trust Type": 2,
+      "Trust Attributes": 26,
+      "Supported Encryption Types": 31,
+      "Salt Principal": "host/localadmember.addom.samba.example.com at ADDOM.SAMBA.EXAMPLE.COM",
+      "Password Last Change": "20181111133742Z",
+      "Password Changes": "1",
+      "Password": {
+        "Change Time": "20181111111111Z",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "%s" },
+      "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" } }
+""" % binascii.unhexlify (TESTPW_1).decode ()
+
+DOMINFO_BLOB_BAD_MALFORMED_SID_1 = """
+    { "Reserved Flags": "0",
+      "Join Time": "20111111111111Z",
+      "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": "host/localadmember.addom.samba.example.com at ADDOM.SAMBA.EXAMPLE.COM",
+      "Password Last Change": "20181111133742Z",
+      "Password Changes": "1",
+      "Password": {
+        "Change Time": "20181111111111Z",
+        "Change Server": "ADDC",
+        "Cleartext Blob": "%s" },
+      "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" } }
+""" % binascii.unhexlify (TESTPW_3).decode ()
+
+class NetPrimarytrustBaseWrapper (object):
+    """
+    Guard the base so it doesn't inherit from TestCase. This prevents it from
+    being run by unittest directly.
+    """
+
+    class NetPrimarytrustTests_Base(samba.tests.BlackboxTestCase):
+        """Blackbox tests for replaying machine account credentials."""
+        force_import    = True # append --force to command line
+        expect_success  = True
+        expect_equalout = True
+        inblob          = DOMINFO_BLOB_OK
+        backupfile      = None
+        delete_secrets  = False
+        secrets_path    = None
+
+        def setUp(self):
+            """
+            Save a backup of the secrets.tdb that the main test manipulates.
+            """
+            super(NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base, self) \
+                .setUp()
+
+            s3conf = s3param.get_context()
+            s3conf.load(self.get_loadparm().configfile)
+            private = s3conf.get("private dir")
+            assert private is not None
+
+            self.secrets_path = SECRETS_PATH_FMT % private
+
+            assert self.check_output("tdbbackup \"%s\"" % self.secrets_path) == b""
+            self.backupfile = "%s.bak" % self.secrets_path
+            if self.delete_secrets is True:
+                os.remove (self.secrets_path)
+
+
+        def tearDown(self):
+            """
+            Move backed up secrets.tdb over the changed file.
+            """
+            super(NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base, self) \
+                .tearDown()
+            if self.backupfile is not None: # only after successful tdbbackup
+                try:
+                    os.rename (self.backupfile, self.secrets_path)
+                except OSError as exn: # some test envs are weird
+                    print("no [%s] during test tearDown() [err: %s]; ignoring"
+                          % (self.backupfile, exn))
+
+        def test_json_import (self):
+            """
+            Postconditions: the command must have terminated zero without
+            generating any messages on stdout. Also, the injection of
+            credentials must have had an effect on the ``secrets.tdb`` used
+            during the test: if there were existing credentials, then the
+            file contents must have changed. Otherwise, a new one must have
+            been created.
+            """
+            argv_r = "%s import%s" % (COMMAND,
+                                      self.force_import and " --force" or "")
+            argv_d = "%s export" % COMMAND
+            failed = False
+            out    = None
+            chksum = None
+            if self.delete_secrets is False:
+                # Secrets must be obtainable as json; hash secrets.tdb.
+                outpre = compat_json_loads (self.check_output (argv_d))
+                chksum = self.hash_file (self.secrets_path)
+
+            try:
+                out = self.check_output_with_stdin(argv_r,
+                                                   stdin=self.inblob.encode (),
+                                                   timeout=CHILD_TIMEOUT)
+            except samba.tests.BlackboxProcessError as e:
+                if self.expect_success is True:
+                    self.fail("Error calling [%s]: %s" % (argv_r, e))
+                failed = True
+
+            if self.expect_success is True:
+                self.assertTrue (out == b"")
+                if chksum is not None:
+                    self.assertTrue (chksum !=
+                                     self.hash_file (self.secrets_path))
+                injs = compat_json_loads (self.inblob)
+
+                try:
+                    outjs = compat_json_loads (self.check_output (argv_d))
+                except samba.tests.BlackboxProcessError as e:
+                    self.fail("Error verifying postconditions while calling "
+                              "[%s]: %s" % (argv_d, e))
+
+                if self.expect_equalout is True:
+                    self.assertTrue (outjs == injs)
+
+                return
+
+            if failed is False:
+                self.fail ("Expected test failure did not occur.")
+            self.assertTrue (out is None)
+
+class NetPrimarytrustOkTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    pass
+
+class NetPrimarytrustOkNextChangeTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    inblob          = DOMINFO_BLOB_OK_NEXT_CHANGE
+    expect_equalout = False # info1->next_change is temporary
+
+class NetPrimarytrustOkMultipassTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    inblob          = DOMINFO_BLOB_OK_MULTIPASS
+
+class NetPrimarytrustBadImportNoForceTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """
+    Samba is joined domain member during the test so ``net primarytrust
+    import`` must not succeed without --force.
+    """
+    expect_success  = False
+    force_import    = False
+
+class NetPrimarytrustOkImportNoForceTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """
+    ``secrets.tdb`` has been erased so ``net primarytrust import`` must succeed
+    without --force.
+    """
+    expect_success  = True
+    force_import    = False
+    delete_secrets  = True
+
+class NetPrimarytrustBadEmptyTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    expect_success  = False
+    inblob          = ""
+
+class NetPrimarytrustBadJunkTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """Reject non-JSON."""
+    expect_success  = False
+    inblob          = """this won't parse as json"""
+
+class NetPrimarytrustBadMalformedJSONTest \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """Reject incomplete JSON fragment."""
+    expect_success  = False
+    inblob          = "{\"Computer Name\": \"LOCALADMEMBER"
+
+class NetPrimarytrustOkMissingTimestamps \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """
+    The fields ``Join Time``, ``Password Last Change``, and ``Change Time`` are
+    absent.
+    """
+    expect_success  = True
+    expect_equalout = False
+    inblob          = DOMINFO_BLOB_OK_MISSING_TIMESTAMPS
+
+class NetPrimarytrustOkMissingKrb5Enctypes \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """
+    Blob lacks ``Supported Encryption Types``; the local ones are substituted.
+    """
+    expect_success  = True
+    expect_equalout = False
+    inblob          = DOMINFO_BLOB_OK_MISSING_ENCTYPES
+
+class NetPrimarytrustBadMissingFields1Test \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """Fail on absence of field ``Trust Flags``."""
+    expect_success  = False
+    inblob          = DOMINFO_BLOB_BAD_MISSING_FIELDS_TRUSTFLAGS
+
+class NetPrimarytrustBadMissingFields2Test \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """Fail on absence of field ``DNS Domain Info->Domain DNS Name``."""
+    expect_success  = False
+    inblob          = DOMINFO_BLOB_BAD_MISSING_FIELDS_DNSDOMAIN
+
+class NetPrimarytrustBadMissingFields3Test \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """
+    Fail on absence of field ``Password->Change Server``. Passwords are
+    optional but they have mandatory fields.
+    """
+    expect_success  = False
+    inblob          = DOMINFO_BLOB_BAD_INCOMPLETE_PASSWORD
+
+class NetPrimarytrustBadMalformedSID1Test \
+        (NetPrimarytrustBaseWrapper.NetPrimarytrustTests_Base):
+    """Garbage SID version field."""
+    expect_success  = False
+    inblob          = DOMINFO_BLOB_BAD_MALFORMED_SID_1
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index b8132086ef8..7fd7083ceff 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -483,9 +483,8 @@ 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")
+planpythontestsuite("ad_member:local", "samba.tests.blackbox.netprimarytrust")
 
 # 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/20190124/d0d339b5/signature.sig>


More information about the samba-technical mailing list