[PATCH v7] add JSON output to net ads

Philipp Gesang philipp.gesang at intra2net.com
Tue Aug 28 07:37:42 UTC 2018


Hi again,

sending the latest revision of the JSON patches to “net ads”,
addressing two issues raised by Andrew Bartlett.

A full CI run is available:
https://gitlab.com/samba-team/devel/samba/pipelines/28780982

Best,
Philipp

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

Changes in v7:
* Cause test failure on samba.tests.BlackboxProcessError.
* Drop `-j' flag, retain long form `--json' only.


Changes in v6:
* Rebase on catalyst/master.
* Don’t catch samba.tests.BlackboxProcessError in tests.
* Move test to the “chgdcpass” set.
* Wrap the test base class so unittest doesn’t attempt to run it.


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 4d6a06e3fe1828fc06c2edfe3719811be4f32f12 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 v7 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 b6d44d304238ee107561d2e14620298028b0ae50 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 v7 2/5] s3: net: implement json output for ads info

Add the switch '--json' 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..2b9c4a26b69 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", 0, 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 d3b19aa3eb53744c9a181d909159eb21f52a229c 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 v7 3/5] s3: net: implement json output for ads lookup

Add JSON printer (option '--json') 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 7df1e429e575e6afcee9ef09f6092176a91e3b73 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 v7 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 27c7a823566fc9010bde8abffc6f0507a0150026 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 v7 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 | 83 ++++++++++++++++++++++
 source4/selftest/tests.py                  |  2 +
 2 files changed, 85 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..44db7cf8455
--- /dev/null
+++ b/python/samba/tests/blackbox/netads_json.py
@@ -0,0 +1,83 @@
+# 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
+
+COMMAND         = "bin/net ads"
+PLAIN_KEY_REGEX = re.compile ("^([^ \t:][^:]*):") # extract keys from non-json version
+
+class BaseWrapper (object):
+    """
+    Guard the base so it doesn't inherit from TestCase. This prevents it from
+    being run by unittest directly.
+    """
+
+    class NetAdsJSONTests_Base(samba.tests.BlackboxTestCase):
+        """Blackbox tests for JSON output of the net ads suite of commands."""
+        subcmd = None
+
+        def setUp(self):
+            super(BaseWrapper.NetAdsJSONTests_Base, self).setUp()
+
+        def test_json_wellformed (self):
+            """The output of ``--json`` commands must parse as JSON."""
+            argv = "%s %s --json" % (COMMAND, self.subcmd)
+            try:
+                out = self.check_output(argv)
+                json.loads (out)
+            except samba.tests.BlackboxProcessError as e:
+                self.fail("Error calling [%s]: %s" % (argv, e))
+
+        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``..
+            """
+            argv = "%s %s" % (COMMAND, self.subcmd)
+            try:
+                out_plain = self.check_output(argv)
+            except samba.tests.BlackboxProcessError as e:
+                self.fail("Error calling [%s]: %s" % (argv, e))
+
+            argv = "%s %s --json" % (COMMAND, self.subcmd)
+            try:
+                out_jsobj = self.check_output(argv)
+            except samba.tests.BlackboxProcessError as e:
+                self.fail("Error calling [%s]: %s" % (argv, e))
+
+            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
+
+class NetAdsJSONInfoTests(BaseWrapper.NetAdsJSONTests_Base):
+    subcmd = "info"
+
+class NetAdsJSONlookupTests(BaseWrapper.NetAdsJSONTests_Base):
+    subcmd = "lookup"
+
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index 0af0504d13a..b7dc4b8ff8c 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -445,6 +445,8 @@ plantestsuite("samba4.blackbox.client_etypes_legacy(ad_dc:client)", "ad_dc:clien
 plantestsuite("samba4.blackbox.client_etypes_strong(ad_dc:client)", "ad_dc:client", [os.path.join(bbdir, "test_client_etypes.sh"), '$DC_SERVER', '$DC_USERNAME', '$DC_PASSWORD', '$PREFIX_ABS', 'strong', '17_18'])
 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_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")
 
 # Tests using the "Simple" NTVFS backend
 for t in ["base.rw1"]:
-- 
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/20180828/84247343/signature-0001.sig>


More information about the samba-technical mailing list