[RFC PATCH v5] add JSON output to net ads

Philipp Gesang philipp.gesang at intra2net.com
Tue Aug 21 06:29:53 UTC 2018


Hey guys,

this iteration addresses the comments to v4 by Andrew Bartlett
(tests, merge conflicts).

CI: https://gitlab.com/phgsng/samba/pipelines/28267867

A note on the tests: Instead of making the code that parses the
non-JSON output more permissive I chose to make the output of
“net ads lookup” more regular. As a consequence, The blackbox
tests now depend on this change. I’m aware that this change is
not entirely related to the patch series. If it is not acceptable
let me know and I’ll tweak the test code accordingly.

Best,
Philipp

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

Changes in v5:

* Rebase on catalyst/master; cleanup branch; resolve conflicts.
* Add rudimentary blackbox tests.
* Include another patch to make the non-JSON output more regular.


Changes in v4:

* Incorporate feedback by Gary Lockyer.
* Change signature of json_to_string() too to take const*
  (necessitated by above item).
* Submit as patch bundle.


Changes in v3:

* Remove an unused variable.
* Rebase onto:
  https://gitlab.com/catalyst-samba/samba/pipelines/25428061
* Full branch available on gitlab:
  https://gitlab.com/phgsng/samba/commits/i2n-net-ads-json
* Add one tiny patch that drops an unnecessary json_free().


Changes in v2:

* Rebase onto the error handling branch:
  https://lists.samba.org/archive/samba-technical/2018-July/128973.html
* Change signature of json_is_invalid() to accept const*.
* Use json_add_guid() instead of dumping uuid manually.


V1 blurb:

The informational commands of the ``net ads'' family format their
output in an ad-hoc manner that is not specified. For automated
processing it would be useful to optionally encode the output as
JSON.

These two patches against master add such a JSON output to ``net
ads info'' and ``net ads lookup''. They have been forward-ported
from the 4.7-ish Samba that we run. Compilation succeeds with
master but I'm still in the process of getting working test
environment, so they're virtually untested.

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

-------------- next part --------------
From 0ee65660bc3ad36bdea96a021cc62386228a1aad Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 9 Jul 2018 09:41:37 +0200
Subject: [PATCH v5 1/5] lib/audit_logging: make json_{is_invalid,to_string}()
 accept a const*

Allow for json_is_invalid() and json_to_string() to be used on a
const pointer. Neither function requires for the json object to
be mutable so constraining them to non-const* is unnecessary.

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

diff --git a/lib/audit_logging/audit_logging.c b/lib/audit_logging/audit_logging.c
index ac08863129a..c274e971925 100644
--- a/lib/audit_logging/audit_logging.c
+++ b/lib/audit_logging/audit_logging.c
@@ -355,7 +355,7 @@ void json_free(struct json_object *object)
  * @return is the object valid?
  *
  */
-bool json_is_invalid(struct json_object *object)
+bool json_is_invalid(const struct json_object *object)
 {
 	return !object->valid;
 }
@@ -907,7 +907,7 @@ int json_add_guid(struct json_object *object,
  * @return A string representation of the object or NULL if the object
  *         is invalid.
  */
-char *json_to_string(TALLOC_CTX *mem_ctx, struct json_object *object)
+char *json_to_string(TALLOC_CTX *mem_ctx, const struct json_object *object)
 {
 	char *json = NULL;
 	char *json_string = NULL;
diff --git a/lib/audit_logging/audit_logging.h b/lib/audit_logging/audit_logging.h
index 4203743f315..5f095c0df9a 100644
--- a/lib/audit_logging/audit_logging.h
+++ b/lib/audit_logging/audit_logging.h
@@ -55,7 +55,7 @@ _WARN_UNUSED_RESULT_ struct json_object json_new_object(void);
 _WARN_UNUSED_RESULT_ struct json_object json_new_array(void);
 void json_free(struct json_object *object);
 void json_assert_is_array(struct json_object *array);
-_WARN_UNUSED_RESULT_ bool json_is_invalid(struct json_object *object);
+_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,
@@ -93,6 +93,6 @@ _WARN_UNUSED_RESULT_ struct json_object json_get_array(
 _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,
-					  struct json_object *object);
+					  const struct json_object *object);
 #endif
 #endif
-- 
2.17.1


From 8444b87a064f8bfb0913eceb9ce89929722ffced Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 2 Jul 2018 16:21:59 +0200
Subject: [PATCH v5 2/5] s3: net: implement json output for ads info

Add the switch '-j' to 'net' to format the output as JSON.

The rationale is to supply the information in a machine-readable
fashion to complement the text version of the output which is
neither particularly well defined nor locale-safe.

The output differs from that of plain 'info' in that times are
not formatted as timestamps.

Currently affects only the 'net ads info' subcommand.

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 source3/utils/net.c         |   1 +
 source3/utils/net.h         |   1 +
 source3/utils/net_ads.c     | 118 ++++++++++++++++++++++++++++++++++++
 source3/utils/wscript_build |   2 +
 4 files changed, 122 insertions(+)

diff --git a/source3/utils/net.c b/source3/utils/net.c
index 69564f65232..502e1ed5f4e 100644
--- a/source3/utils/net.c
+++ b/source3/utils/net.c
@@ -974,6 +974,7 @@ static struct functable net_func[] = {
 		/* Options for 'net ads join or leave' */
 		{"no-dns-updates", 0, POPT_ARG_NONE, &c->opt_no_dns_updates},
 		{"keep-account", 0, POPT_ARG_NONE, &c->opt_keep_account},
+		{"json", 'j', POPT_ARG_NONE, &c->opt_json},
 		POPT_COMMON_SAMBA
 		{ 0, 0, 0, 0}
 	};
diff --git a/source3/utils/net.h b/source3/utils/net.h
index 5e70fd3aafa..0d01ad45010 100644
--- a/source3/utils/net.h
+++ b/source3/utils/net.h
@@ -86,6 +86,7 @@ struct net_context {
 	const char *opt_precheck;
 	int opt_no_dns_updates;
 	int opt_keep_account;
+	int opt_json;
 
 	int opt_have_ip;
 	struct sockaddr_storage opt_dest_ip;
diff --git a/source3/utils/net_ads.c b/source3/utils/net_ads.c
index afe47dad839..15f14220831 100644
--- a/source3/utils/net_ads.c
+++ b/source3/utils/net_ads.c
@@ -173,6 +173,120 @@ static int net_ads_lookup(struct net_context *c, int argc, const char **argv)
 }
 
 
+#ifdef HAVE_JANSSON
+#include <jansson.h>
+#include "audit_logging.h" /* various JSON helpers */
+#include "auth/common_auth.h"
+
+/*
+ * note: JSON output deliberately bypasses gettext so as to provide the same
+ * output irrespective of the locale.
+ */
+
+static int net_ads_info_json(ADS_STRUCT *ads)
+{
+	int ret = 0;
+	char addr[INET6_ADDRSTRLEN];
+	time_t pass_time;
+	struct json_object jsobj = json_new_object();
+	TALLOC_CTX *ctx = NULL;
+	char *json = NULL;
+
+	if (json_is_invalid(&jsobj)) {
+		d_fprintf(stderr, _("error setting up JSON value\n"));
+
+		goto failure;
+	}
+
+	pass_time = secrets_fetch_pass_last_set_time(ads->server.workgroup);
+
+	print_sockaddr(addr, sizeof(addr), &ads->ldap.ss);
+
+	ret = json_add_string (&jsobj, "LDAP server", addr);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string (&jsobj, "LDAP server name", ads->config.ldap_server_name);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string (&jsobj, "Realm", ads->config.realm);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string (&jsobj, "Bind Path", ads->config.bind_path);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_int (&jsobj, "LDAP port", ads->ldap.port);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_int (&jsobj, "Server time", ads->config.current_time);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string (&jsobj, "KDC server", ads->auth.kdc_server);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_int (&jsobj, "Server time offset", ads->auth.time_offset);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_int (&jsobj, "Last machine account password change", pass_time);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	if (json_is_invalid(&jsobj)) {
+		ret = -1;
+		goto failure;
+	}
+
+	ctx = talloc_new(NULL);
+	if (ctx == NULL) {
+		ret = -1;
+		d_fprintf(stderr, _("Out of memory\n"));
+
+		goto failure;
+	}
+
+	json = json_to_string(ctx, &jsobj);
+	if (json) {
+		d_printf("%s\n", json);
+	} else {
+		ret = -1;
+		d_fprintf(stderr, _("error encoding to JSON\n"));
+	}
+
+	TALLOC_FREE(ctx);
+failure:
+	ads_destroy(&ads);
+
+	return ret;
+}
+
+#else /* [HAVE_JANSSON] */
+
+static int net_ads_info_json(ADS_STRUCT *)
+{
+	d_fprintf(stderr, _("JSON support not available\n"));
+
+	return -1;
+}
+
+#endif /* [HAVE_JANSSON] */
+
+
 
 static int net_ads_info(struct net_context *c, int argc, const char **argv)
 {
@@ -208,6 +322,10 @@ static int net_ads_info(struct net_context *c, int argc, const char **argv)
 		d_fprintf( stderr, _("Failed to get server's current time!\n"));
 	}
 
+	if (c->opt_json) {
+		return net_ads_info_json(ads);
+	}
+
 	pass_time = secrets_fetch_pass_last_set_time(ads->server.workgroup);
 
 	print_sockaddr(addr, sizeof(addr), &ads->ldap.ss);
diff --git a/source3/utils/wscript_build b/source3/utils/wscript_build
index 93e6abaac0d..ba7ab1201b0 100644
--- a/source3/utils/wscript_build
+++ b/source3/utils/wscript_build
@@ -249,6 +249,8 @@ bld.SAMBA3_BINARY('net',
                  printing_migrate
                  trusts_util
                  IDMAP_AUTORID_TDB
+                 jansson
+                 common_auth
                  ''')
 
 bld.SAMBA3_BINARY('mvxattr',
-- 
2.17.1


From f8b1502d4695cce75ba98eab3ee8d953460e5951 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Tue, 3 Jul 2018 12:09:17 +0200
Subject: [PATCH v5 3/5] s3: net: implement json output for ads lookup

Add JSON printer (option '-j') for the 'net ads lookup' command.
This outputs the same information as the plain version, with
integral ({LMNT,LM20} Token, NT Version) and boolean values
(Flags) not stringified.

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 source3/utils/net_ads.c | 280 +++++++++++++++++++++++++++++++++++-----
 1 file changed, 248 insertions(+), 32 deletions(-)

diff --git a/source3/utils/net_ads.c b/source3/utils/net_ads.c
index 15f14220831..ae83f8baa08 100644
--- a/source3/utils/net_ads.c
+++ b/source3/utils/net_ads.c
@@ -41,6 +41,12 @@
 #include "lib/param/loadparm.h"
 #include "utils/net_dns.h"
 
+#ifdef HAVE_JANSSON
+#include <jansson.h>
+#include "audit_logging.h" /* various JSON helpers */
+#include "auth/common_auth.h"
+#endif /* [HAVE_JANSSON] */
+
 #ifdef HAVE_ADS
 
 /* when we do not have sufficient input parameters to contact a remote domain
@@ -55,6 +61,242 @@ static const char *assume_own_realm(struct net_context *c)
 	return NULL;
 }
 
+#ifdef HAVE_JANSSON
+
+/*
+ * note: JSON output deliberately bypasses gettext so as to provide the same
+ * output irrespective of the locale.
+ */
+
+static int output_json(const struct json_object *jsobj)
+{
+	TALLOC_CTX *ctx = NULL;
+	char *json = NULL;
+
+	if (json_is_invalid(jsobj)) {
+		return -1;
+	}
+
+	ctx = talloc_new(NULL);
+	if (ctx == NULL) {
+		d_fprintf(stderr, _("Out of memory\n"));
+		return -1;
+	}
+
+	json = json_to_string(ctx, jsobj);
+	if (!json) {
+		d_fprintf(stderr, _("error encoding to JSON\n"));
+		return -1;
+	}
+
+	d_printf("%s\n", json);
+	TALLOC_FREE(ctx);
+
+	return 0;
+}
+
+static int net_ads_cldap_netlogon_json(ADS_STRUCT *ads,
+				       const char *addr,
+				       const struct NETLOGON_SAM_LOGON_RESPONSE_EX *reply)
+{
+	struct json_object jsobj = json_new_object();
+	struct json_object flagsobj = json_new_object();
+	char response_type [32] = { '\0' };
+	int ret = 0;
+
+	if (json_is_invalid(&jsobj) || json_is_invalid(&flagsobj)) {
+		d_fprintf(stderr, _("error setting up JSON value\n"));
+
+		goto failure;
+	}
+
+	switch (reply->command) {
+		case LOGON_SAM_LOGON_USER_UNKNOWN_EX:
+			strncpy(response_type, "LOGON_SAM_LOGON_USER_UNKNOWN_EX",
+	      sizeof(response_type));
+			break;
+		case LOGON_SAM_LOGON_RESPONSE_EX:
+			strncpy(response_type, "LOGON_SAM_LOGON_RESPONSE_EX",
+	      sizeof(response_type));
+			break;
+		default:
+			snprintf(response_type, sizeof(response_type), "0x%x",
+	       reply->command);
+			break;
+	}
+
+	ret = json_add_string(&jsobj, "Information for Domain Controller", addr);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Response Type", response_type);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_guid(&jsobj, "GUID", &reply->domain_uuid);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is a PDC", reply->server_type & NBT_SERVER_PDC);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is a GC of the forest", reply->server_type & NBT_SERVER_GC);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is an LDAP server", reply->server_type & NBT_SERVER_LDAP);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Supports DS", reply->server_type & NBT_SERVER_DS);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is running a KDC", reply->server_type & NBT_SERVER_KDC);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is running time services", reply->server_type & NBT_SERVER_TIMESERV);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is the closest DC", reply->server_type & NBT_SERVER_CLOSEST);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is writable", reply->server_type & NBT_SERVER_WRITABLE);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Has a hardware clock", reply->server_type & NBT_SERVER_GOOD_TIMESERV);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is a non-domain NC serviced by LDAP server", reply->server_type & NBT_SERVER_NDNC);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is NT6 DC that has some secrets", reply->server_type & NBT_SERVER_SELECT_SECRET_DOMAIN_6);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is NT6 DC that has all secrets", reply->server_type & NBT_SERVER_FULL_SECRET_DOMAIN_6);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Runs Active Directory Web Services", reply->server_type & NBT_SERVER_ADS_WEB_SERVICE);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Runs on Windows 2012 or later", reply->server_type & NBT_SERVER_DS_8);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Forest", reply->forest);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Domain", reply->dns_domain);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Domain Controller", reply->pdc_dns_name);
+	if (ret != 0) {
+		goto failure;
+	}
+
+
+	ret = json_add_string(&jsobj, "Pre-Win2k Domain", reply->domain_name);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Pre-Win2k Hostname", reply->pdc_name);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	if (*reply->user_name) {
+		ret = json_add_string(&jsobj, "User name", reply->user_name);
+		if (ret != 0) {
+			goto failure;
+		}
+	}
+
+	ret = json_add_string(&jsobj, "Server Site Name", reply->server_site);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Client Site Name", reply->client_site);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_int(&jsobj, "NT Version", reply->nt_version);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_int(&jsobj, "LMNT Token", reply->lmnt_token);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_int(&jsobj, "LM20 Token", reply->lm20_token);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = json_add_object(&jsobj, "Flags", &flagsobj);
+	if (ret != 0) {
+		goto failure;
+	}
+
+	ret = output_json(&jsobj);
+	json_free(&jsobj); /* frees flagsobj recursively */
+
+	return ret;
+
+failure:
+	json_free(&flagsobj);
+	json_free(&jsobj);
+
+	return ret;
+}
+
+#else /* [HAVE_JANSSON] */
+
+static int net_ads_cldap_netlogon_json(ADS_STRUCT *, const char *,
+				       const struct NETLOGON_SAM_LOGON_RESPONSE_EX *)
+{
+	d_fprintf(stderr, _("JSON support not available\n"));
+
+	return -1;
+}
+
+#endif /* [HAVE_JANSSON] */
+
 /*
   do a cldap netlogon query
 */
@@ -70,6 +312,10 @@ static int net_ads_cldap_netlogon(struct net_context *c, ADS_STRUCT *ads)
 		return -1;
 	}
 
+	if (c->opt_json) {
+		return net_ads_cldap_netlogon_json(ads, addr, &reply);
+	}
+
 	d_printf(_("Information for Domain Controller: %s\n\n"),
 		addr);
 
@@ -174,14 +420,6 @@ static int net_ads_lookup(struct net_context *c, int argc, const char **argv)
 
 
 #ifdef HAVE_JANSSON
-#include <jansson.h>
-#include "audit_logging.h" /* various JSON helpers */
-#include "auth/common_auth.h"
-
-/*
- * note: JSON output deliberately bypasses gettext so as to provide the same
- * output irrespective of the locale.
- */
 
 static int net_ads_info_json(ADS_STRUCT *ads)
 {
@@ -189,8 +427,6 @@ static int net_ads_info_json(ADS_STRUCT *ads)
 	char addr[INET6_ADDRSTRLEN];
 	time_t pass_time;
 	struct json_object jsobj = json_new_object();
-	TALLOC_CTX *ctx = NULL;
-	char *json = NULL;
 
 	if (json_is_invalid(&jsobj)) {
 		d_fprintf(stderr, _("error setting up JSON value\n"));
@@ -247,29 +483,9 @@ static int net_ads_info_json(ADS_STRUCT *ads)
 		goto failure;
 	}
 
-	if (json_is_invalid(&jsobj)) {
-		ret = -1;
-		goto failure;
-	}
-
-	ctx = talloc_new(NULL);
-	if (ctx == NULL) {
-		ret = -1;
-		d_fprintf(stderr, _("Out of memory\n"));
-
-		goto failure;
-	}
-
-	json = json_to_string(ctx, &jsobj);
-	if (json) {
-		d_printf("%s\n", json);
-	} else {
-		ret = -1;
-		d_fprintf(stderr, _("error encoding to JSON\n"));
-	}
-
-	TALLOC_FREE(ctx);
+	ret = output_json(&jsobj);
 failure:
+	json_free(&jsobj);
 	ads_destroy(&ads);
 
 	return ret;
-- 
2.17.1


From 762f3fb1805793c26b7171f3fed8370e7a1cf7e1 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 20 Aug 2018 15:10:31 +0200
Subject: [PATCH v5 4/5] s3: net: normalize output of lookup subcommand

Use spaces and tabs consistently following the majority of the
printed output: tabs only for indenting, no space before the
colon separator, a single space after the separator.

The irregularities in formatting date back to the original commit
2c029a8b96..

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 source3/utils/net_ads.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/source3/utils/net_ads.c b/source3/utils/net_ads.c
index ae83f8baa08..0022bba18ab 100644
--- a/source3/utils/net_ads.c
+++ b/source3/utils/net_ads.c
@@ -365,17 +365,17 @@ static int net_ads_cldap_netlogon(struct net_context *c, ADS_STRUCT *ads)
 		   (reply.server_type & NBT_SERVER_DS_8) ? _("yes") : _("no"));
 
 
-	printf(_("Forest:\t\t\t%s\n"), reply.forest);
-	printf(_("Domain:\t\t\t%s\n"), reply.dns_domain);
-	printf(_("Domain Controller:\t%s\n"), reply.pdc_dns_name);
+	printf(_("Forest: %s\n"), reply.forest);
+	printf(_("Domain: %s\n"), reply.dns_domain);
+	printf(_("Domain Controller: %s\n"), reply.pdc_dns_name);
 
-	printf(_("Pre-Win2k Domain:\t%s\n"), reply.domain_name);
-	printf(_("Pre-Win2k Hostname:\t%s\n"), reply.pdc_name);
+	printf(_("Pre-Win2k Domain: %s\n"), reply.domain_name);
+	printf(_("Pre-Win2k Hostname: %s\n"), reply.pdc_name);
 
-	if (*reply.user_name) printf(_("User name:\t%s\n"), reply.user_name);
+	if (*reply.user_name) printf(_("User name: %s\n"), reply.user_name);
 
-	printf(_("Server Site Name :\t\t%s\n"), reply.server_site);
-	printf(_("Client Site Name :\t\t%s\n"), reply.client_site);
+	printf(_("Server Site Name: %s\n"), reply.server_site);
+	printf(_("Client Site Name: %s\n"), reply.client_site);
 
 	d_printf(_("NT Version: %d\n"), reply.nt_version);
 	d_printf(_("LMNT Token: %.2x\n"), reply.lmnt_token);
-- 
2.17.1


From 532bfa54dd3729e64d27a583cebec0daa4ebb78c Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 20 Aug 2018 14:50:39 +0200
Subject: [PATCH v5 5/5] tests/blackbox: add test for net ads JSON output

Implement blackbox tests for

    $ net ads info --json
    $ net ads lookup --json

that validate

    a) JSON wellformedness (by feeding it into the JSON library
       that ships with Python), and
    b) equality of the set of keys printed to that of the
       non-JSON version.

Signed-off-by: Philipp Gesang <philipp.gesang at intra2net.com>
---
 python/samba/tests/blackbox/netads_json.py | 71 ++++++++++++++++++++++
 source4/selftest/tests.py                  |  1 +
 2 files changed, 72 insertions(+)
 create mode 100644 python/samba/tests/blackbox/netads_json.py

diff --git a/python/samba/tests/blackbox/netads_json.py b/python/samba/tests/blackbox/netads_json.py
new file mode 100644
index 00000000000..3c002c59fa5
--- /dev/null
+++ b/python/samba/tests/blackbox/netads_json.py
@@ -0,0 +1,71 @@
+# Blackbox tests for the "net ads ... --json" commands
+# Copyright (C) 2018 Intra2net AG
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import json
+import re
+
+import samba.tests
+from samba.tests import BlackboxTestCase
+
+COMMAND         = "bin/net ads"
+PLAIN_KEY_REGEX = re.compile ("^([^ \t:][^:]*):") # extract keys from non-json version
+
+class NetAdsJSONTests_Base(samba.tests.BlackboxTestCase):
+    """Blackbox tests for JSON output of the net ads suite of commands."""
+    subcmd = None
+
+    def setUp(self):
+        super(NetAdsJSONTests_Base, self).setUp()
+
+    def test_json_wellformed (self):
+        """The output of ``--json`` commands must parse as JSON."""
+        try:
+            out = self.check_output("%s %s --json" % (COMMAND, self.subcmd))
+            json.loads (out)
+        except samba.tests.BlackboxProcessError:
+            pass
+
+    def test_json_matching_entries (self):
+        """
+        The ``--json`` variants must contain the same keys as their respective
+        plain counterpart.
+
+        Does not check nested dictionaries (e. g. the ``Flags`` value of ``net
+        ads lookup``..
+        """
+        try:
+            out_plain = self.check_output("%s %s"        % (COMMAND, self.subcmd))
+            out_jsobj = self.check_output("%s %s --json" % (COMMAND, self.subcmd))
+
+            parsed = json.loads (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:" ]:
+                self.assertTrue (parsed.get (key) is not None)
+                del parsed [key]
+
+            self.assertTrue (len (parsed) == 0) # tolerate no leftovers
+        except samba.tests.BlackboxProcessError:
+            pass
+
+class NetAdsJSONInfoTests(NetAdsJSONTests_Base):
+    subcmd = "info"
+
+class NetAdsJSONlookupTests(NetAdsJSONTests_Base):
+    subcmd = "lookup"
+
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index 0af0504d13a..8141b32ad59 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -792,6 +792,7 @@ for env in ["ad_dc_ntvfs:local", "ad_dc:local",
             "fl2003dc:local", "fl2008r2dc:local",
             "promoted_dc:local"]:
     planoldpythontestsuite(env, "samba.tests.blackbox.smbcontrol")
+    planoldpythontestsuite(env, "samba.tests.blackbox.netads_json")
 
 plantestsuite_loadlist("samba4.ldap.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [python, os.path.join(samba4srcdir, "dsdb/tests/python/ldap.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT'])
 plantestsuite_loadlist("samba4.tokengroups.krb5.python(ad_dc_ntvfs)", "ad_dc_ntvfs:local", [python, os.path.join(samba4srcdir, "dsdb/tests/python/token_group.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '-k', 'yes', '$LOADLIST', '$LISTOPT'])
-- 
2.17.1

-------------- 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/20180821/26298b73/signature.sig>


More information about the samba-technical mailing list