[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, >stamp);
+ 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