Unverified Commit b14170a6 authored by Ph. Gesang's avatar Ph. Gesang 💾
Browse files

lib/audit_logging: define JSON object converters and getters



Add helpers for decoding JSON from strings and fds. The inputs
may be objects, array, or any other JSON type. Add a new
constructor ``json_new_null()`` to make this more obvious.

Note that libjansson added json_loadfd() with version 2.10 so
we use that one if it is available, otherwise we fall back to
a stdio based implementation.

Also add helpers for the inverse operations of the
``json_add_{string,sid,base64}()'' APIs. Following the existing
``json_get_*()'' functions these do not require a TALLOC_CTX to
be passed.
Signed-off-by: Ph. Gesang's avatarPhilipp Gesang <philipp.gesang@intra2net.com>
parent 19442d14
......@@ -85,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,
......@@ -97,10 +97,58 @@ 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 "read";
case JSON_TRUE: return "true";
case JSON_FALSE: return "false";
case JSON_NULL: return "null";
}
__builtin_unreachable();
}
/*
* @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.
*
......@@ -108,7 +156,7 @@ const struct json_object json_empty_object = {.valid = false, .root = NULL};
*
* @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_json(const char* prefix,
......@@ -331,6 +379,32 @@ 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.
......@@ -485,6 +559,77 @@ 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(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 = 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.
*
......@@ -897,7 +1042,7 @@ int json_add_base64(struct json_object *object,
s = base64_encode_data_blob
(ctx,
(DATA_BLOB) { .data = discard_const_p (uint8_t, data)
(DATA_BLOB) { .data = discard_const_p(uint8_t, data)
, .length = len });
if (s == NULL) {
......@@ -1022,6 +1167,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.
*
......@@ -1103,4 +1300,205 @@ struct json_object json_get_object(struct json_object *object, const char *name)
}
return o;
}
#ifdef HAVE_RECENT_JANSSON
/*
* @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_fd(struct json_object *dst, int jsfd)
{
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 (jsfd < 0) {
DBG_ERR("Invalid file descriptor passed for reading\n");
return JSON_ERROR;
}
object = json_empty_object;
object.root = json_loadfd(jsfd, 0, &jserr);
if (object.root == NULL) {
DBG_ERR("error at position %d:%d (%d B) while reading JSON "
"object from fd %d: source=%s description=%s\n",
jserr.line, jserr.column, jserr.position, jsfd,
jserr.source, jserr.text);
json_free(&object);
return JSON_ERROR;
}
json_free(dst);
object.valid = true;
*dst = object;
return 0;
}
#else /* [HAVE_RECENT_JANSSON] */
int json_from_fd(struct json_object *dst, int jsfd)
{
struct json_object object;
json_error_t jserr;
FILE *jsf = NULL;
int ret;
if (dst == NULL) {
DBG_ERR("Invalid destination for decoding JSON, cannot assign "
"to NULL\n");
return JSON_ERROR;
}
if (jsfd < 0) {
DBG_ERR("Invalid file descriptor passed for reading\n");
return JSON_ERROR;
}
errno = 0;
jsf = fdopen(jsfd, "r");
if (jsf == NULL) {
/* extra error path that can't happen in the fd based version */
DBG_ERR("Could not obtain stdio file object from fd=%d, "
"errno=%d: %m\n",
jsfd, errno);
return JSON_ERROR;
}
object = json_empty_object;
object.root = json_loadf(jsf, 0, &jserr);
if (object.root == NULL) {
DBG_ERR("error at position %d:%d (%d B) while reading JSON "
"object from fd %d: source=%s description=%s\n",
jserr.line, jserr.column, jserr.position, jsfd,
jserr.source, jserr.text);
return JSON_ERROR;
}
errno = 0;
ret = fclose(jsf);
if (ret == EOF) {
/* extra error path that can't happen in the fd based version */
DBG_ERR("error closing stdio file object obtained from fd=%d "
"errno=%d: %m",
jsfd, errno);
json_free(&object);
return JSON_ERROR;
}
json_free(dst);
object.valid = true;
*dst = object;
return 0;
}
#endif /* [HAVE_RECENT_JANSSON] */
/*
* @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 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_sid(const struct json_object *object,
const char *name,
struct dom_sid *sid)
{
int ret = 0;
const char *data = NULL;
struct dom_sid tmp = { 0 };
/* 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;
}
ret = json_get_string(object, name, &data);
if (ret != 0 || data == NULL) {
DBG_ERR("Failed to get string value from JSON object\n");
return JSON_ERROR;
}
if (!dom_sid_parse(data, &tmp)) {
DBG_ERR("Failed to process string value as SID\n");
return JSON_ERROR;
}
sid_copy(sid, &tmp);
return 0;
}
/*
* @brief Get base64 encoded data from a JSON object.
*
* Retrieve the JSON string member at `key' and return its decoded value
* as binary data.
*
* @param object The JSON object to access.
* @param name Key.
* @param value Pointer to the destination blob.
*
* @return 0 the operation was successful
* -1 the operation failed
*/
int json_get_base64(const struct json_object *object,
const char *name,
DATA_BLOB *dst)
{
int ret = 0;
const char *raw = NULL;
DATA_BLOB tmp = { 0 };
/* object and name are checked in json_get_string() */
if (dst == NULL) {
DBG_ERR("Invalid argument, refusing to operate on NULL "
"target\n");
return JSON_ERROR;
}
ret = json_get_string(object, name, &raw);
if (ret != 0 || raw == NULL) {
DBG_ERR("Failed to get string value from JSON object\n");
return JSON_ERROR;
}
if (raw [0] == '\0') {
*dst = data_blob_const(NULL, 0);
return 0;
}
tmp = base64_decode_data_blob(raw);
if (tmp.data == NULL && tmp.length == 0) {
DBG_ERR("Failed to decode string at key [%s] as base64", name);
return JSON_ERROR;
}
*dst = tmp;
return 0;
}
#endif
......@@ -22,6 +22,7 @@
#include "lib/messaging/irpc.h"
#include "lib/tsocket/tsocket.h"
#include "lib/util/attr.h"
#include "lib/util/data_blob.h"
_WARN_UNUSED_RESULT_ char *audit_get_timestamp(TALLOC_CTX *frame);
void audit_log_human_text(const char *prefix,
......@@ -53,6 +54,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);
......@@ -92,11 +94,25 @@ _WARN_UNUSED_RESULT_ int json_add_base64(struct json_object *object,
const uint8_t *data,
const size_t len);
_WARN_UNUSED_RESULT_ int json_get_string(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_base64(const struct json_object *object,
const char *name,
DATA_BLOB *dst);
_WARN_UNUSED_RESULT_ struct json_object json_get_array(
struct json_object *object, const char *name);
_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_fd(struct json_object *dst, int jsfd);
_WARN_UNUSED_RESULT_ int json_from_string(struct json_object *dst,
const char *jsdata);
#endif
#endif
......@@ -282,6 +282,10 @@ def configure(conf):
if conf.CHECK_CFG(package='jansson', args='--cflags --libs',
msg='Checking for jansson'):
conf.CHECK_FUNCS_IN('json_object', 'jansson')
# jansson 2.10 added json_loadfd()
if tuple (map (int, conf.check_cfg(modversion='jansson').split ("."))) \
>= (2, 10):
conf.define('HAVE_RECENT_JANSSON', '1')
if not conf.CONFIG_GET('HAVE_JSON_OBJECT'):
if Options.options.with_json != False:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment