[RFC PATCH v4] add JSON output to net ads

Gary Lockyer gary at catalyst.net.nz
Mon Aug 20 03:57:58 UTC 2018


I've rebased the patches on master, I was intending to write the tests
for this.  But for reasons that I don't understand the 'json' option
does not appear to work.

  net ads info --json

  Invalid option --json: unknown option
  Usage:
    Use 'net help rpc' ...
    ...

Sadly I've run out of time for this but can any one see what I've missed.

Thanks
Gary.





On 02/08/18 14:27, Andrew Bartlett via samba-technical wrote:
> On Fri, 2018-07-13 at 13:54 +0200, Philipp Gesang wrote:
>> Hi again,
>>
>> attached is the fourth iteration of the JSON patches for “net
>> ads” (CI: https://gitlab.com/phgsng/samba/pipelines/25703977).
>>
>> Description in lieu of a cover letter:
>>
>> -8<----------------------------------------------------------->8-
>>
>> 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.
>>
> 
>>
>> 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-
> 
> Thank you so much Philipp! 
> 
> Sorry for the delay in this, there are just two more things I need.
> 
> We need a testsuite for this, for example using python, the
> samba.tests.BlackboxTestCase class and the self.check_output()
> function.
> 
> python/samba/tests/blackbox/samba_dnsupdate.py would be a good example,
> and then load the output with a the python JSON lib (eg json.loads())
> 
> That will ensure we don't start outputting random non-json stuff in
> there, and that this keeps working.
> 
> Finally, someone else beat you to master and so the patch doesn't
> apply.  It doesn't look like major damage after you drop the changes
> Gary already merged, but isn't much but we need it rebased.  
> 
> Andrew Bartlett
> 
-------------- next part --------------
From b7f4c871d1e311d3e13b661591ba039e869e254f Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 20 Aug 2018 10:46:48 +1200
Subject: [PATCH 1/4] 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 ac08863..c274e97 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 4203743..5f095c0 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.7.4


From 332b014ab03ce838e106709415f47a4854ba619f Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang at intra2net.com>
Date: Mon, 9 Jul 2018 14:05:56 +0200
Subject: [PATCH 2/4] lib/audit_logging: avoid freeing unallocated json value

json_free() has no effect on the empty json value so the call is
redundant.

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

diff --git a/lib/audit_logging/audit_logging.c b/lib/audit_logging/audit_logging.c
index c274e97..c9473cc 100644
--- a/lib/audit_logging/audit_logging.c
+++ b/lib/audit_logging/audit_logging.c
@@ -962,7 +962,6 @@ struct json_object json_get_array(struct json_object *object, const char *name)
 	if (json_is_invalid(object)) {
 		DBG_ERR("Invalid JSON object, unable to get array [%s]\n",
 			name);
-		json_free(&array);
 		return array;
 	}
 
-- 
2.7.4


From d5b22e5bcf70585f6c24f33687430dd470d906df 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 3/4] 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 69564f6..502e1ed 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 5e70fd3..0d01ad4 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 afe47da..8a80532 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) {
+		goto failure;
+	}
+
+	ret = json_add_string (&jsobj, "LDAP server name", ads->config.ldap_server_name);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_string (&jsobj, "Realm", ads->config.realm);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_string (&jsobj, "Bind Path", ads->config.bind_path);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_int (&jsobj, "LDAP port", ads->ldap.port);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_int (&jsobj, "Server time", ads->config.current_time);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_string (&jsobj, "KDC server", ads->auth.kdc_server);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_int (&jsobj, "Server time offset", ads->auth.time_offset);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_int (&jsobj, "Last machine account password change", pass_time);
+	if (ret) {
+		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 93e6aba..ba7ab12 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.7.4


From 8aafe2a5b754d81abc0c797c665a841acf74ff28 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 4/4] 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 8a80532..776312b 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) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Response Type", response_type);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_guid(&jsobj, "GUID", &reply->domain_uuid);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is a PDC", reply->server_type & NBT_SERVER_PDC);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is a GC of the forest", reply->server_type & NBT_SERVER_GC);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is an LDAP server", reply->server_type & NBT_SERVER_LDAP);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Supports DS", reply->server_type & NBT_SERVER_DS);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is running a KDC", reply->server_type & NBT_SERVER_KDC);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is running time services", reply->server_type & NBT_SERVER_TIMESERV);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is the closest DC", reply->server_type & NBT_SERVER_CLOSEST);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is writable", reply->server_type & NBT_SERVER_WRITABLE);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Has a hardware clock", reply->server_type & NBT_SERVER_GOOD_TIMESERV);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Is a non-domain NC serviced by LDAP server", reply->server_type & NBT_SERVER_NDNC);
+	if (ret) {
+		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) {
+		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) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Runs Active Directory Web Services", reply->server_type & NBT_SERVER_ADS_WEB_SERVICE);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_bool(&flagsobj, "Runs on Windows 2012 or later", reply->server_type & NBT_SERVER_DS_8);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Forest", reply->forest);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Domain", reply->dns_domain);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Domain Controller", reply->pdc_dns_name);
+	if (ret) {
+		goto failure;
+	}
+
+
+	ret = json_add_string(&jsobj, "Pre-Win2k Domain", reply->domain_name);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Pre-Win2k Hostname", reply->pdc_name);
+	if (ret) {
+		goto failure;
+	}
+
+	if (*reply->user_name) {
+		ret = json_add_string(&jsobj, "User name", reply->user_name);
+		if (ret) {
+			goto failure;
+		}
+	}
+
+	ret = json_add_string(&jsobj, "Server Site Name", reply->server_site);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_string(&jsobj, "Client Site Name", reply->client_site);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_int(&jsobj, "NT Version", reply->nt_version);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_int(&jsobj, "LMNT Token", reply->lmnt_token);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_int(&jsobj, "LM20 Token", reply->lm20_token);
+	if (ret) {
+		goto failure;
+	}
+
+	ret = json_add_object(&jsobj, "Flags", &flagsobj);
+	if (ret) {
+		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.7.4

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 473 bytes
Desc: OpenPGP digital signature
URL: <http://lists.samba.org/pipermail/samba-technical/attachments/20180820/5fe5224c/signature.sig>


More information about the samba-technical mailing list