From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- lib/audit_logging/audit_logging.c | 1356 ++++++++++++++++++++ lib/audit_logging/audit_logging.h | 113 ++ lib/audit_logging/tests/audit_logging_error_test.c | 901 +++++++++++++ lib/audit_logging/tests/audit_logging_test.c | 919 +++++++++++++ lib/audit_logging/wscript_build | 60 + 5 files changed, 3349 insertions(+) create mode 100644 lib/audit_logging/audit_logging.c create mode 100644 lib/audit_logging/audit_logging.h create mode 100644 lib/audit_logging/tests/audit_logging_error_test.c create mode 100644 lib/audit_logging/tests/audit_logging_test.c create mode 100644 lib/audit_logging/wscript_build (limited to 'lib/audit_logging') diff --git a/lib/audit_logging/audit_logging.c b/lib/audit_logging/audit_logging.c new file mode 100644 index 0000000..8ed15ed --- /dev/null +++ b/lib/audit_logging/audit_logging.c @@ -0,0 +1,1356 @@ +/* + common routines for audit logging + + Copyright (C) Andrew Bartlett 2018 + + 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 . +*/ + +/* + * Error handling: + * + */ + +#include "includes.h" + +#include "librpc/ndr/libndr.h" +#include "lib/tsocket/tsocket.h" +#include "libcli/security/dom_sid.h" +#include "libcli/security/security_token.h" +#include "lib/messaging/messaging.h" +#include "auth/common_auth.h" +#include "audit_logging.h" +#include "auth/authn_policy.h" + +/* + * @brief Get a human readable timestamp. + * + * Returns the current time formatted as + * "Tue, 14 Mar 2017 08:38:42.209028 NZDT" + * + * The returned string is allocated by talloc in the supplied context. + * It is the callers responsibility to free it. + * + * @param mem_ctx talloc memory context that owns the returned string. + * + * @return a human readable time stamp, or NULL in the event of an error. + * + */ +char* audit_get_timestamp(TALLOC_CTX *frame) +{ + char buffer[40]; /* formatted time less usec and timezone */ + char tz[10]; /* formatted time zone */ + struct tm* tm_info; /* current local time */ + struct timeval tv; /* current system time */ + int ret; /* response code */ + char * ts; /* formatted time stamp */ + + ret = gettimeofday(&tv, NULL); + if (ret != 0) { + DBG_ERR("Unable to get time of day: (%d) %s\n", + errno, + strerror(errno)); + return NULL; + } + + tm_info = localtime(&tv.tv_sec); + if (tm_info == NULL) { + DBG_ERR("Unable to determine local time\n"); + return NULL; + } + + strftime(buffer, sizeof(buffer)-1, "%a, %d %b %Y %H:%M:%S", tm_info); + strftime(tz, sizeof(tz)-1, "%Z", tm_info); + ts = talloc_asprintf(frame, "%s.%06ld %s", buffer, (long)tv.tv_usec, tz); + if (ts == NULL) { + DBG_ERR("Out of memory formatting time stamp\n"); + } + return ts; +} + +/* + * @brief write an audit message to the audit logs. + * + * Write a human readable text audit message to the samba logs. + * + * @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_level The debug level to log the message with. + */ +void audit_log_human_text(const char* prefix, + const char* message, + int debug_class, + int debug_level) +{ + DEBUGC(debug_class, debug_level, ("%s %s\n", prefix, message)); +} + +#ifdef HAVE_JANSSON +/* + * Constant for empty json object initialisation + */ +const struct json_object json_empty_object = {.valid = false, .root = NULL}; +/* + * @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_level The debug level to log the message with. + */ +void audit_log_json(struct json_object* message, + int debug_class, + int debug_level) +{ + TALLOC_CTX *frame = NULL; + char *s = NULL; + + if (json_is_invalid(message)) { + DBG_ERR("Invalid JSON object, unable to log\n"); + return; + } + + frame = talloc_stackframe(); + s = json_to_string(frame, message); + if (s == NULL) { + DBG_ERR("json_to_string returned NULL, " + "JSON audit message could not written\n"); + TALLOC_FREE(frame); + return; + } + /* + * This is very strange, but we call this routine to get a log + * output without the header. JSON logs all have timestamps + * so this only makes parsing harder. + * + * We push out the raw JSON blob without a prefix, consumers + * can find such lines by the leading { + */ + DEBUGADDC(debug_class, debug_level, ("%s\n", s)); + TALLOC_FREE(frame); +} + +/* + * @brief get a connection to the messaging event server. + * + * Get a connection to the messaging event server registered by server_name. + * + * @param msg_ctx a valid imessaging_context. + * @param server_name name of messaging event server to connect to. + * @param server_id The event server details to populate + * + * @return NTSTATUS + */ +static NTSTATUS get_event_server( + struct imessaging_context *msg_ctx, + const char *server_name, + struct server_id *event_server) +{ + NTSTATUS status; + TALLOC_CTX *frame = talloc_stackframe(); + unsigned num_servers, i; + struct server_id *servers; + + status = irpc_servers_byname( + msg_ctx, + frame, + server_name, + &num_servers, + &servers); + + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Failed to find the target '%s' on the message bus " + "to send JSON audit events to: %s\n", + server_name, + nt_errstr(status)); + TALLOC_FREE(frame); + return status; + } + + /* + * Select the first server that is listening, because we get + * connection refused as NT_STATUS_OBJECT_NAME_NOT_FOUND + * without waiting + */ + for (i = 0; i < num_servers; i++) { + status = imessaging_send( + msg_ctx, + servers[i], + MSG_PING, + &data_blob_null); + if (NT_STATUS_IS_OK(status)) { + *event_server = servers[i]; + TALLOC_FREE(frame); + return NT_STATUS_OK; + } + } + DBG_NOTICE( + "Failed to find '%s' registered on the message bus to " + "send JSON audit events to: %s\n", + server_name, + nt_errstr(status)); + TALLOC_FREE(frame); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; +} + +/* + * @brief send an audit message to a messaging event server. + * + * Send the message to a registered and listening event server. + * Note: Any errors are logged, and the message is not sent. This is to ensure + * that a poorly behaved event server does not impact Samba. + * + * As it is possible to lose messages, especially during server + * shut down, currently this function is primarily intended for use + * in integration tests. + * + * @param msg_ctx an imessaging_context, can be NULL in which case no message + * will be sent. + * @param server_name the naname of the event server to send the message to. + * @param messag_type A message type defined in librpc/idl/messaging.idl + * @param message The message to send. + * + */ +void audit_message_send( + struct imessaging_context *msg_ctx, + const char *server_name, + uint32_t message_type, + struct json_object *message) +{ + struct server_id event_server = { + .pid = 0, + }; + NTSTATUS status; + + const char *message_string = NULL; + DATA_BLOB message_blob = data_blob_null; + TALLOC_CTX *ctx = NULL; + + if (json_is_invalid(message)) { + DBG_ERR("Invalid JSON object, unable to send\n"); + return; + } + if (msg_ctx == NULL) { + DBG_DEBUG("No messaging context\n"); + return; + } + + ctx = talloc_new(NULL); + if (ctx == NULL) { + DBG_ERR("Out of memory creating temporary context\n"); + return; + } + + /* Need to refetch the address each time as the destination server may + * have disconnected and reconnected in the interim, in which case + * messages may get lost + */ + status = get_event_server(msg_ctx, server_name, &event_server); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(ctx); + return; + } + + message_string = json_to_string(ctx, message); + message_blob = data_blob_string_const(message_string); + status = imessaging_send( + msg_ctx, + event_server, + message_type, + &message_blob); + + /* + * If the server crashed, try to find it again + */ + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + status = get_event_server(msg_ctx, server_name, &event_server); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(ctx); + return; + } + imessaging_send( + msg_ctx, + event_server, + message_type, + &message_blob); + } + TALLOC_FREE(ctx); +} + +/* + * @brief Create a new struct json_object, wrapping a JSON Object. + * + * 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 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_object(void) { + + struct json_object object = json_empty_object; + + object.root = json_object(); + if (object.root == NULL) { + object.valid = false; + DBG_ERR("Unable to create JSON object\n"); + return object; + } + object.valid = true; + return object; +} + +/* + * @brief Create a new struct json_object wrapping a JSON Array. + * + * 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 implementation + * allocates memory with malloc and not talloc. + * + * @return a struct json_object, error will be set to true if the array + * could not be created. + * + */ +struct json_object json_new_array(void) { + + struct json_object array = json_empty_object; + + array.root = json_array(); + if (array.root == NULL) { + array.valid = false; + DBG_ERR("Unable to create JSON array\n"); + return array; + } + array.valid = true; + return array; +} + + +/* + * @brief free and invalidate a previously created JSON object. + * + * Release any resources owned by a json_object, and then mark the structure + * as invalid. It is safe to call this multiple times on an object. + * + */ +void json_free(struct json_object *object) +{ + if (object->root != NULL) { + json_decref(object->root); + } + object->root = NULL; + object->valid = false; +} + +/* + * @brief is the current JSON object invalid? + * + * Check the state of the object to determine if it is invalid. + * + * @return is the object valid? + * + */ +bool json_is_invalid(const struct json_object *object) +{ + return !object->valid; +} + +/* + * @brief Add an integer value to a JSON object. + * + * Add an integer value named 'name' to the json object. + * + * @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_int(struct json_object *object, const char *name, const json_int_t value) +{ + int ret = 0; + json_t *integer = NULL; + + if (json_is_invalid(object)) { + DBG_ERR("Unable to add int [%s] value [%jd], " + "target object is invalid\n", + name, + (intmax_t)value); + return JSON_ERROR; + } + + integer = json_integer(value); + if (integer == NULL) { + DBG_ERR("Unable to create integer value [%s] value [%jd]\n", + name, + (intmax_t)value); + return JSON_ERROR; + } + + ret = json_object_set_new(object->root, name, integer); + if (ret != 0) { + json_decref(integer); + DBG_ERR("Unable to add int [%s] value [%jd]\n", + name, + (intmax_t)value); + } + return ret; +} + +/* + * @brief Add a boolean value to a JSON object. + * + * Add a boolean value named 'name' to the json object. + * + * @param object the JSON object to be updated. + * @param name the name. + * @param value the value. + * + * @return 0 the operation was successful + * -1 the operation failed + * + */ +int json_add_bool(struct json_object *object, + const char *name, + const bool value) +{ + int ret = 0; + + if (json_is_invalid(object)) { + DBG_ERR("Unable to add boolean [%s] value [%d], " + "target object is invalid\n", + name, + value); + return JSON_ERROR; + } + + ret = json_object_set_new(object->root, name, json_boolean(value)); + if (ret != 0) { + DBG_ERR("Unable to add boolean [%s] value [%d]\n", name, value); + } + return ret; +} + +/* + * @brief Add an optional boolean value to a JSON object. + * + * Add an optional boolean value named 'name' to the json object. + * + * @param object the JSON object to be updated. + * @param name the name. + * @param value the value. + * + * @return 0 the operation was successful + * -1 the operation failed + * + */ +int json_add_optional_bool(struct json_object *object, + const char *name, + const bool *value) +{ + int ret = 0; + + if (json_is_invalid(object)) { + DBG_ERR("Unable to add boolean [%s] value [%d], " + "target object is invalid\n", + name, + *value); + return JSON_ERROR; + } + + if (value != NULL) { + ret = json_object_set_new(object->root, name, json_boolean(*value)); + if (ret != 0) { + DBG_ERR("Unable to add boolean [%s] value [%d]\n", name, *value); + return ret; + } + } else { + ret = json_object_set_new(object->root, name, json_null()); + if (ret != 0) { + DBG_ERR("Unable to add null boolean [%s]\n", name); + return ret; + } + } + + return ret; +} + +/* + * @brief Add a string value to a JSON object. + * + * Add a string value named 'name' to the json object. + * + * @param object the JSON object to be updated. + * @param name the name. + * @param value the value. + * + * @return 0 the operation was successful + * -1 the operation failed + * + */ +int json_add_string(struct json_object *object, + const char *name, + const char *value) +{ + int ret = 0; + + if (json_is_invalid(object)) { + DBG_ERR("Unable to add string [%s], target object is invalid\n", + name); + return JSON_ERROR; + } + if (value) { + json_t *string = json_string(value); + if (string == NULL) { + DBG_ERR("Unable to add string [%s], " + "could not create string object\n", + name); + return JSON_ERROR; + } + ret = json_object_set_new(object->root, name, string); + if (ret != 0) { + json_decref(string); + DBG_ERR("Unable to add string [%s]\n", name); + return ret; + } + } else { + ret = json_object_set_new(object->root, name, json_null()); + if (ret != 0) { + DBG_ERR("Unable to add null string [%s]\n", name); + return ret; + } + } + return ret; +} + +/* + * @brief Assert that the current JSON object is an array. + * + * Check that the current object is a JSON array, and if not + * invalidate the object. We also log an error message as this indicates + * bug in the calling code. + * + * @param object the JSON object to be validated. + */ +void json_assert_is_array(struct json_object *array) { + + if (json_is_invalid(array)) { + return; + } + + if (json_is_array(array->root) == false) { + DBG_ERR("JSON object is not an array\n"); + array->valid = false; + return; + } +} + +/* + * @brief Add a JSON object to a JSON object. + * + * Add a JSON object named 'name' to the json object. + * + * @param object the JSON object to be updated. + * @param name the name. + * @param value the value. + * + * @return 0 the operation was successful + * -1 the operation failed + * + */ +int json_add_object(struct json_object *object, + const char *name, + struct json_object *value) +{ + int ret = 0; + json_t *jv = NULL; + + if (value != NULL && json_is_invalid(value)) { + DBG_ERR("Invalid JSON object [%s] supplied\n", name); + return JSON_ERROR; + } + if (json_is_invalid(object)) { + DBG_ERR("Unable to add object [%s], target object is invalid\n", + name); + return JSON_ERROR; + } + + jv = value == NULL ? json_null() : value->root; + + if (json_is_array(object->root)) { + ret = json_array_append_new(object->root, jv); + } else if (json_is_object(object->root)) { + ret = json_object_set_new(object->root, name, jv); + } else { + DBG_ERR("Invalid JSON object type\n"); + ret = JSON_ERROR; + } + if (ret != 0) { + DBG_ERR("Unable to add object [%s]\n", name); + } + return ret; +} + +/* + * @brief Add a string to a JSON object, truncating if necessary. + * + * + * Add a string value named 'name' to the json object, the string will be + * truncated if it is more than len characters long. If len is 0 the value + * is encoded as a JSON null. + * + * + * @param object the JSON object to be updated. + * @param name the name. + * @param value the value. + * @param len the maximum number of characters to be copied. + * + * @return 0 the operation was successful + * -1 the operation failed + * + */ +int json_add_stringn(struct json_object *object, + const char *name, + const char *value, + const size_t len) +{ + + int ret = 0; + if (json_is_invalid(object)) { + DBG_ERR("Unable to add string [%s], target object is invalid\n", + name); + return JSON_ERROR; + } + + if (value != NULL && len > 0) { + json_t *string = json_stringn(value, len); + if (string == NULL) { + DBG_ERR("Unable to add string [%s], " + "could not create string object\n", + name); + return JSON_ERROR; + } + ret = json_object_set_new(object->root, name, string); + if (ret != 0) { + json_decref(string); + DBG_ERR("Unable to add string [%s]\n", name); + return ret; + } + } else { + ret = json_object_set_new(object->root, name, json_null()); + if (ret != 0) { + DBG_ERR("Unable to add null string [%s]\n", name); + return ret; + } + } + return ret; +} + +/* + * @brief Add a version object to a JSON object + * + * Add a version object to the JSON object + * "version":{"major":1, "minor":0} + * + * The version tag is intended to aid the processing of the JSON messages + * The major version number should change when an attribute is: + * - renamed + * - removed + * - its meaning changes + * - its contents change format + * The minor version should change whenever a new attribute is added and for + * minor bug fixes to an attributes content. + * + * + * @param object the JSON object to be updated. + * @param major the major version number + * @param minor the minor version number + * + * @return 0 the operation was successful + * -1 the operation failed + */ +int json_add_version(struct json_object *object, int major, int minor) +{ + int ret = 0; + struct json_object version; + + if (json_is_invalid(object)) { + DBG_ERR("Unable to add version, target object is invalid\n"); + return JSON_ERROR; + } + + version = json_new_object(); + if (json_is_invalid(&version)) { + DBG_ERR("Unable to add version, failed to create object\n"); + return JSON_ERROR; + } + ret = json_add_int(&version, "major", major); + if (ret != 0) { + json_free(&version); + return ret; + } + ret = json_add_int(&version, "minor", minor); + if (ret != 0) { + json_free(&version); + return ret; + } + ret = json_add_object(object, "version", &version); + if (ret != 0) { + json_free(&version); + return ret; + } + return ret; +} + +/* + * @brief add an ISO 8601 timestamp to the object. + * + * Add a date and time as a timestamp in ISO 8601 format to a JSON object + * + * "time":"2017-03-06T17:18:04.455081+1300" + * + * + * @param object the JSON object to be updated. + * @param name the name. + * @param time the value to set. + * + * @return 0 the operation was successful + * -1 the operation failed + */ +int json_add_time(struct json_object *object, const char *name, const struct timeval tv) +{ + char buffer[40]; /* formatted time less usec and timezone */ + char timestamp[65]; /* the formatted ISO 8601 time stamp */ + char tz[10]; /* formatted time zone */ + struct tm* tm_info; /* current local time */ + int ret; /* return code from json operations */ + + if (json_is_invalid(object)) { + DBG_ERR("Unable to add time, target object is invalid\n"); + return JSON_ERROR; + } + + tm_info = localtime(&tv.tv_sec); + if (tm_info == NULL) { + DBG_ERR("Unable to determine local time\n"); + return JSON_ERROR; + } + + strftime(buffer, sizeof(buffer)-1, "%Y-%m-%dT%T", tm_info); + strftime(tz, sizeof(tz)-1, "%z", tm_info); + snprintf( + timestamp, + sizeof(timestamp), + "%s.%06ld%s", + buffer, + tv.tv_usec, + tz); + ret = json_add_string(object, name, timestamp); + if (ret != 0) { + DBG_ERR("Unable to add time to JSON object\n"); + } + return ret; +} + +/* + * @brief add an ISO 8601 timestamp to the object. + * + * Add the current date and time as a timestamp in ISO 8601 format + * to a JSON object + * + * "timestamp":"2017-03-06T17:18:04.455081+1300" + * + * + * @param object the JSON object to be updated. + * + * @return 0 the operation was successful + * -1 the operation failed + */ +int json_add_timestamp(struct json_object *object) +{ + struct timeval tv; /* current system time */ + int r; /* response code from gettimeofday */ + + if (json_is_invalid(object)) { + DBG_ERR("Unable to add time stamp, target object is invalid\n"); + return JSON_ERROR; + } + + r = gettimeofday(&tv, NULL); + if (r) { + DBG_ERR("Unable to get time of day: (%d) %s\n", + errno, + strerror(errno)); + return JSON_ERROR; + } + + return json_add_time(object, "timestamp", tv); +} + +/* + *@brief Add a tsocket_address to a JSON object + * + * Add the string representation of a Samba tsocket_address to the object. + * + * "localAddress":"ipv6::::0" + * + * + * @param object the JSON object to be updated. + * @param name the name. + * @param address the tsocket_address. + * + * @return 0 the operation was successful + * -1 the operation failed + * + */ +int json_add_address(struct json_object *object, + const char *name, + const struct tsocket_address *address) +{ + int ret = 0; + + if (json_is_invalid(object)) { + DBG_ERR("Unable to add address [%s], " + "target object is invalid\n", + name); + return JSON_ERROR; + } + + if (address == NULL) { + ret = json_object_set_new(object->root, name, json_null()); + if (ret != 0) { + DBG_ERR("Unable to add null address [%s]\n", name); + return JSON_ERROR; + } + } else { + TALLOC_CTX *ctx = talloc_new(NULL); + char *s = NULL; + + if (ctx == NULL) { + DBG_ERR("Out of memory adding address [%s]\n", name); + return JSON_ERROR; + } + + s = tsocket_address_string(address, ctx); + if (s == NULL) { + DBG_ERR("Out of memory adding address [%s]\n", name); + TALLOC_FREE(ctx); + return JSON_ERROR; + } + ret = json_add_string(object, name, s); + if (ret != 0) { + DBG_ERR( + "Unable to add address [%s] value [%s]\n", name, s); + TALLOC_FREE(ctx); + return JSON_ERROR; + } + TALLOC_FREE(ctx); + } + return ret; +} + +/* + * @brief Add a formatted string representation of a sid to a json object. + * + * Add the string representation of a Samba sid to the object. + * + * "sid":"S-1-5-18" + * + * + * @param object the JSON object to be updated. + * @param name the name. + * @param sid the sid + * + * @return 0 the operation was successful + * -1 the operation failed + * + */ +int json_add_sid(struct json_object *object, + const char *name, + const struct dom_sid *sid) +{ + int ret = 0; + + if (json_is_invalid(object)) { + DBG_ERR("Unable to add SID [%s], " + "target object is invalid\n", + name); + return JSON_ERROR; + } + + if (sid == NULL) { + ret = json_object_set_new(object->root, name, json_null()); + if (ret != 0) { + DBG_ERR("Unable to add null SID [%s]\n", name); + return ret; + } + } else { + struct dom_sid_buf sid_buf; + + ret = json_add_string( + object, name, dom_sid_str_buf(sid, &sid_buf)); + if (ret != 0) { + DBG_ERR("Unable to add SID [%s] value [%s]\n", + name, + sid_buf.buf); + return ret; + } + } + return ret; +} + +/* + * @brief Add a formatted string representation of a guid to a json object. + * + * Add the string representation of a Samba GUID to the object. + * + * "guid":"1fb9f2ee-2a4d-4bf8-af8b-cb9d4529a9ab" + * + * + * @param object the JSON object to be updated. + * @param name the name. + * @param guid the guid. + * + * @return 0 the operation was successful + * -1 the operation failed + * + * + */ +int json_add_guid(struct json_object *object, + const char *name, + const struct GUID *guid) +{ + + int ret = 0; + + if (json_is_invalid(object)) { + DBG_ERR("Unable to add GUID [%s], " + "target object is invalid\n", + name); + return JSON_ERROR; + } + + if (guid == NULL) { + ret = json_object_set_new(object->root, name, json_null()); + if (ret != 0) { + DBG_ERR("Unable to add null GUID [%s]\n", name); + return ret; + } + } else { + char *guid_str; + struct GUID_txt_buf guid_buff; + + guid_str = GUID_buf_string(guid, &guid_buff); + ret = json_add_string(object, name, guid_str); + if (ret != 0) { + DBG_ERR("Unable to add GUID [%s] value [%s]\n", + name, + guid_str); + return ret; + } + } + return ret; +} + +/* + * @brief Add a hex-formatted string representation of a 32-bit integer to a + * json object. + * + * Add a hex-formatted string representation of a 32-bit flags integer to the + * object. + * + * "accountFlags":"0x12345678" + * + * + * @param object the JSON object to be updated. + * @param name the name. + * @param flags the flags. + * + * @return 0 the operation was successful + * -1 the operation failed + * + * + */ +int json_add_flags32(struct json_object *object, + const char *name, + const uint32_t flags) +{ + int ret = 0; + char buf[sizeof("0x12345678")]; + + if (json_is_invalid(object)) { + DBG_ERR("Unable to add flags [%s], " + "target object is invalid\n", + name); + return JSON_ERROR; + } + + ret = snprintf(buf, sizeof (buf), "0x%08X", flags); + if (ret != sizeof (buf) - 1) { + DBG_ERR("Unable to format flags [%s] value [0x%08X]\n", + name, + flags); + return JSON_ERROR; + } + + ret = json_add_string(object, name, buf); + if (ret != 0) { + DBG_ERR("Unable to add flags [%s] value [%s]\n", + name, + buf); + } + + return ret; +} + +/* + * @brief Replaces the object for a given key with a given json object. + * + * If key already exists, the value will be replaced. Otherwise the given + * value will be added under the given key. + * + * @param object the JSON object to be updated. + * @param key the key which will be updated. + * @param new_obj the new value object to be inserted. + * + * @return 0 the operation was successful + * -1 the operation failed (e.j. if one of the parameters is invalid) + */ +int json_update_object(struct json_object *object, + const char *key, + struct json_object *new_obj) +{ + int ret = 0; + + if (json_is_invalid(object)) { + DBG_ERR("Unable to update key [%s], " + "target object is invalid\n", + key); + return JSON_ERROR; + } + if (json_is_invalid(new_obj)) { + DBG_ERR("Unable to update key [%s], " + "new object is invalid\n", + key); + return JSON_ERROR; + } + + if (key == NULL) { + DBG_ERR("Unable to add null String as key\n"); + return JSON_ERROR; + } + + ret = json_object_set(object->root, key, new_obj->root); + if (ret != 0) { + DBG_ERR("Unable to update object\n"); + return ret; + } + + return ret; +} + +/* + * @brief Convert a JSON object into a string + * + * 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. + * + * @param mem_ctx the talloc memory context owning the returned string + * @param object the json object. + * + * @return A string representation of the object or NULL if the object + * is invalid. + */ +char *json_to_string(TALLOC_CTX *mem_ctx, const struct json_object *object) +{ + char *json = NULL; + char *json_string = NULL; + + if (json_is_invalid(object)) { + DBG_ERR("Invalid JSON object, unable to convert to string\n"); + return NULL; + } + + if (object->root == NULL) { + return NULL; + } + + /* + * json_dumps uses malloc, so need to call free(json) to release + * the memory + */ + json = json_dumps(object->root, 0); + if (json == NULL) { + DBG_ERR("Unable to convert JSON object to string\n"); + return NULL; + } + + json_string = talloc_strdup(mem_ctx, json); + if (json_string == NULL) { + free(json); + DBG_ERR("Unable to copy JSON object string to talloc string\n"); + return NULL; + } + free(json); + + return json_string; +} + +/* + * @brief get a json array named "name" from the json object. + * + * Get the array attribute named name, creating it if it does not exist. + * + * @param object the json object. + * @param name the name of the array attribute + * + * @return The array object, will be created if it did not exist. + */ +struct json_object json_get_array(struct json_object *object, const char *name) +{ + + struct json_object array = json_empty_object; + json_t *a = NULL; + int ret = 0; + + if (json_is_invalid(object)) { + DBG_ERR("Invalid JSON object, unable to get array [%s]\n", + name); + json_free(&array); + return array; + } + + array = json_new_array(); + if (json_is_invalid(&array)) { + DBG_ERR("Unable to create new array for [%s]\n", name); + return array; + } + + a = json_object_get(object->root, name); + if (a == NULL) { + return array; + } + + ret = json_array_extend(array.root, a); + if (ret != 0) { + DBG_ERR("Unable to get array [%s]\n", name); + json_free(&array); + return array; + } + + return array; +} + +/* + * @brief get a json object named "name" from the json object. + * + * Get the object attribute named name, creating it if it does not exist. + * + * @param object the json object. + * @param name the name of the object attribute + * + * @return The object, will be created if it did not exist. + */ +struct json_object json_get_object(struct json_object *object, const char *name) +{ + + struct json_object o = json_new_object(); + json_t *v = NULL; + int ret = 0; + + if (json_is_invalid(&o)) { + DBG_ERR("Unable to get object [%s]\n", name); + json_free(&o); + return o; + } + + if (json_is_invalid(object)) { + DBG_ERR("Invalid JSON object, unable to get object [%s]\n", + name); + json_free(&o); + return o; + } + + v = json_object_get(object->root, name); + if (v == NULL) { + return o; + } + ret = json_object_update(o.root, v); + if (ret != 0) { + DBG_ERR("Unable to get object [%s]\n", name); + json_free(&o); + return o; + } + return o; +} + +/* + * @brief Return the JSON null object. + * + * @return the JSON null object. + */ +_WARN_UNUSED_RESULT_ struct json_object json_null_object(void) +{ + struct json_object object = json_empty_object; + + object.root = json_null(); + if (object.root != NULL) { + object.valid = true; + } + + return object; +} + +/* + * @brief Create a JSON object from a structure containing audit information. + * + * @param audit_info the audit information from which to create a JSON object. + * + * @return the JSON object (which may be valid or not) + * + * + */ +struct json_object json_from_audit_info(const struct authn_audit_info *audit_info) +{ + struct json_object object = json_new_object(); + enum auth_event_id_type auth_event_id; + const struct auth_user_info_dc *client_info = NULL; + const char *policy_name = NULL; + const char *silo_name = NULL; + const bool *policy_enforced = NULL; + NTSTATUS policy_status; + struct authn_int64_optional tgt_lifetime_mins; + const char *location = NULL; + const char *audit_event = NULL; + const char *audit_reason = NULL; + int rc = 0; + + if (json_is_invalid(&object)) { + goto failure; + } + + auth_event_id = authn_audit_info_event_id(audit_info); + rc = json_add_int(&object, "eventId", auth_event_id); + if (rc != 0) { + goto failure; + } + + policy_name = authn_audit_info_policy_name(audit_info); + rc = json_add_string(&object, "policyName", policy_name); + if (rc != 0) { + goto failure; + } + + silo_name = authn_audit_info_silo_name(audit_info); + rc = json_add_string(&object, "siloName", silo_name); + if (rc != 0) { + goto failure; + } + + policy_enforced = authn_audit_info_policy_enforced(audit_info); + rc = json_add_optional_bool(&object, "policyEnforced", policy_enforced); + if (rc != 0) { + goto failure; + } + + policy_status = authn_audit_info_policy_status(audit_info); + rc = json_add_string(&object, "status", nt_errstr(policy_status)); + if (rc != 0) { + goto failure; + } + + tgt_lifetime_mins = authn_audit_info_policy_tgt_lifetime_mins(audit_info); + if (tgt_lifetime_mins.is_present) { + rc = json_add_int(&object, "tgtLifetime", tgt_lifetime_mins.val); + if (rc != 0) { + goto failure; + } + } + + location = authn_audit_info_location(audit_info); + rc = json_add_string(&object, "location", location); + if (rc != 0) { + goto failure; + } + + audit_event = authn_audit_info_event(audit_info); + rc = json_add_string(&object, "auditEvent", audit_event); + if (rc != 0) { + goto failure; + } + + audit_reason = authn_audit_info_reason(audit_info); + rc = json_add_string(&object, "reason", audit_reason); + if (rc != 0) { + goto failure; + } + + client_info = authn_audit_info_client_info(audit_info); + if (client_info != NULL) { + const struct auth_user_info *client_user_info = NULL; + + client_user_info = client_info->info; + if (client_user_info != NULL) { + rc = json_add_string(&object, "checkedDomain", client_user_info->domain_name); + if (rc != 0) { + goto failure; + } + + rc = json_add_string(&object, "checkedAccount", client_user_info->account_name); + if (rc != 0) { + goto failure; + } + + rc = json_add_string(&object, "checkedLogonServer", client_user_info->logon_server); + if (rc != 0) { + goto failure; + } + + rc = json_add_flags32(&object, "checkedAccountFlags", client_user_info->acct_flags); + if (rc != 0) { + goto failure; + } + } + + if (client_info->num_sids) { + const struct dom_sid *policy_checked_sid = NULL; + + policy_checked_sid = &client_info->sids[PRIMARY_USER_SID_INDEX].sid; + rc = json_add_sid(&object, "checkedSid", policy_checked_sid); + if (rc != 0) { + goto failure; + } + } + } + + return object; + +failure: + json_free(&object); + return object; +} + +#endif diff --git a/lib/audit_logging/audit_logging.h b/lib/audit_logging/audit_logging.h new file mode 100644 index 0000000..a8bcf6f --- /dev/null +++ b/lib/audit_logging/audit_logging.h @@ -0,0 +1,113 @@ +/* + common routines for audit logging + + Copyright (C) Andrew Bartlett 2018 + + 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 . +*/ +#ifndef _AUDIT_LOGGING_H_ +#define _AUDIT_LOGGING_H_ +#include +#include "lib/messaging/irpc.h" +#include "lib/tsocket/tsocket.h" +#include "lib/util/attr.h" + +_WARN_UNUSED_RESULT_ char *audit_get_timestamp(TALLOC_CTX *frame); +void audit_log_human_text(const char *prefix, + const char *message, + int debug_class, + int debug_level); + +#ifdef HAVE_JANSSON +#include +/* + * Wrapper for jannson JSON object + * + */ +struct json_object { + json_t *root; + bool valid; +}; +extern const struct json_object json_empty_object; + +#define JSON_ERROR -1 + +void audit_log_json(struct json_object *message, + int debug_class, + int debug_level); +void audit_message_send(struct imessaging_context *msg_ctx, + const char *server_name, + uint32_t message_type, + struct json_object *message); +_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(const struct json_object *object); + +_WARN_UNUSED_RESULT_ int json_add_int(struct json_object *object, + const char *name, + const json_int_t value); +_WARN_UNUSED_RESULT_ int json_add_bool(struct json_object *object, + const char *name, + const bool value); +_WARN_UNUSED_RESULT_ int json_add_optional_bool(struct json_object *object, + const char *name, + const bool *value); +_WARN_UNUSED_RESULT_ int json_add_string(struct json_object *object, + const char *name, + const char *value); +_WARN_UNUSED_RESULT_ int json_add_object(struct json_object *object, + const char *name, + struct json_object *value); +_WARN_UNUSED_RESULT_ int json_add_stringn(struct json_object *object, + const char *name, + const char *value, + const size_t len); +_WARN_UNUSED_RESULT_ int json_add_version(struct json_object *object, + int major, + int minor); +_WARN_UNUSED_RESULT_ int json_add_time(struct json_object *object, const char *name, struct timeval tv); +_WARN_UNUSED_RESULT_ int json_add_timestamp(struct json_object *object); +_WARN_UNUSED_RESULT_ int json_add_address( + struct json_object *object, + const char *name, + const struct tsocket_address *address); +_WARN_UNUSED_RESULT_ int json_add_sid(struct json_object *object, + const char *name, + const struct dom_sid *sid); +_WARN_UNUSED_RESULT_ int json_add_guid(struct json_object *object, + const char *name, + const struct GUID *guid); + +_WARN_UNUSED_RESULT_ int json_add_flags32(struct json_object *object, + const char *name, + uint32_t flags); + +_WARN_UNUSED_RESULT_ int json_update_object(struct json_object *object, + const char *key, + struct json_object *new_obj); + +_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_ struct json_object json_null_object(void); +struct authn_audit_info; +_WARN_UNUSED_RESULT_ struct json_object json_from_audit_info( + const struct authn_audit_info *audit_info); +#endif +#endif diff --git a/lib/audit_logging/tests/audit_logging_error_test.c b/lib/audit_logging/tests/audit_logging_error_test.c new file mode 100644 index 0000000..12a81ff --- /dev/null +++ b/lib/audit_logging/tests/audit_logging_error_test.c @@ -0,0 +1,901 @@ +/* + * Unit tests for the audit_logging library. + * + * Copyright (C) Andrew Bartlett 2018 + * + * 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 . + * + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ + +/* + * Unit tests for lib/audit_logging/audit_logging.c + * + * These tests exercise the error handling code and mock the jannson functions + * to trigger errors. + * + */ +#include +#include +#include +#include + +#include "includes.h" + +#include "librpc/ndr/libndr.h" +#include "lib/tsocket/tsocket.h" +#include "libcli/security/dom_sid.h" +#include "lib/messaging/messaging.h" +#include "auth/common_auth.h" + +#include "lib/audit_logging/audit_logging.h" + +const int JANSSON_FAILURE = -1; +const int CALL_ORIG = -2; + +/* + * cmocka wrappers for json_object + */ +json_t *__wrap_json_object(void); +json_t *__real_json_object(void); +json_t *__wrap_json_object(void) +{ + + bool fail = (bool)mock(); + if (fail) { + return NULL; + } + return __real_json_object(); +} + +/* + * cmocka wrappers for json_array + */ +json_t *__wrap_json_array(void); +json_t *__real_json_array(void); +json_t *__wrap_json_array(void) +{ + + bool fail = (bool)mock(); + if (fail) { + return NULL; + } + return __real_json_array(); +} + +/* + * cmoka wrappers for json_integer + */ +json_t *__wrap_json_integer(json_int_t value); +json_t *__real_json_integer(json_int_t value); +json_t *__wrap_json_integer(json_int_t value) +{ + + bool fail = (bool)mock(); + if (fail) { + return NULL; + } + return __real_json_integer(value); +} + +/* + * cmocka wrappers for json_string + */ +json_t *__wrap_json_string(const char *value); +json_t *__real_json_string(const char *value); +json_t *__wrap_json_string(const char *value) +{ + + bool fail = (bool)mock(); + if (fail) { + return NULL; + } + return __real_json_string(value); +} + +/* + * cmocka wrappers for json_stringn + */ +json_t *__wrap_json_stringn(const char *value, size_t len); +json_t *__real_json_stringn(const char *value, size_t len); +json_t *__wrap_json_stringn(const char *value, size_t len) +{ + + bool fail = (bool)mock(); + if (fail) { + return NULL; + } + return __real_json_stringn(value, len); +} + +/* + * cmocka wrappers for json_dumps + */ +char *__wrap_json_dumps(const json_t *json, size_t flags); +char *__real_json_dumps(const json_t *json, size_t flags); +char *__wrap_json_dumps(const json_t *json, size_t flags) +{ + + bool fail = (bool)mock(); + if (fail) { + return NULL; + } + return __real_json_dumps(json, flags); +} + +/* + * cmocka wrappers for json_object_set_new + */ +int __wrap_json_object_set_new(json_t *object, const char *key, json_t *value); +int __real_json_object_set_new(json_t *object, const char *key, json_t *value); +int __wrap_json_object_set_new(json_t *object, const char *key, json_t *value) +{ + int rc = (int)mock(); + if (rc != CALL_ORIG) { + return rc; + } + return __real_json_object_set_new(object, key, value); +} + +/* + * cmocka wrappers for json_array_append_new + */ +int __wrap_json_array_append_new(json_t *object, + const char *key, + json_t *value); +int __real_json_array_append_new(json_t *object, + const char *key, + json_t *value); +int __wrap_json_array_append_new(json_t *object, const char *key, json_t *value) +{ + int rc = (int)mock(); + if (rc != CALL_ORIG) { + return rc; + } + return __real_json_array_append_new(object, key, value); +} + +/* + * cmocka wrappers for json_array_extend + */ +int __wrap_json_array_extend(json_t *array, json_t *other_array); +int __real_json_array_extend(json_t *array, json_t *other_array); +int __wrap_json_array_extend(json_t *array, json_t *other_array) +{ + + int rc = (int)mock(); + if (rc != CALL_ORIG) { + return rc; + } + return __real_json_array_extend(array, other_array); +} + +/* + * cmocka wrappers for json_object_update + */ +int __wrap_json_object_update(json_t *object, json_t *other_object); +int __real_json_object_update(json_t *object, json_t *other_object); +int __wrap_json_object_update(json_t *object, json_t *other_object) +{ + + int rc = (int)mock(); + if (rc != CALL_ORIG) { + return rc; + } + return __real_json_array_extend(object, other_object); +} + +/* + * cmocka wrappers for gettimeofday + */ +int __wrap_gettimeofday(struct timeval *tv, struct timezone *tz); +int __real_gettimeofday(struct timeval *tv, struct timezone *tz); +int __wrap_gettimeofday(struct timeval *tv, struct timezone *tz) +{ + + int rc = (int)mock(); + if (rc != 0) { + return rc; + } + return __real_gettimeofday(tv, tz); +} + +/* + * cmocka wrappers for localtime + */ +struct tm *__wrap_localtime(const time_t *timep); +struct tm *__real_localtime(const time_t *timep); +struct tm *__wrap_localtime(const time_t *timep) +{ + bool fail = (bool)mock(); + if (fail) { + return NULL; + } + return __real_localtime(timep); +} + +/* + * cmocka wrappers for talloc_named_const + */ +static const void *REAL_TALLOC = "Here"; + +void *__wrap_talloc_named_const(const void *context, + size_t size, + const char *name); +void *__real_talloc_named_const(const void *context, + size_t size, + const char *name); +void *__wrap_talloc_named_const(const void *context, + size_t size, + const char *name) +{ + + void *ret = (void *)mock(); + + if (ret == NULL) { + return NULL; + } + return __real_talloc_named_const(context, size, name); +} + +/* + * cmocka wrappers for talloc_strdup + */ +char *__wrap_talloc_strdup(const void *t, const char *p); +char *__real_talloc_strdup(const void *t, const char *p); +char *__wrap_talloc_strdup(const void *t, const char *p) +{ + + void *ret = (void *)mock(); + + if (ret == NULL) { + return NULL; + } + return __real_talloc_strdup(t, p); +} + +char *__wrap_tsocket_address_string(const struct tsocket_address *addr, + TALLOC_CTX *mem_ctx); +char *__real_tsocket_address_string(const struct tsocket_address *addr, + TALLOC_CTX *mem_ctx); +char *__wrap_tsocket_address_string(const struct tsocket_address *addr, + TALLOC_CTX *mem_ctx) +{ + + bool fail = (bool)mock(); + if (fail) { + return NULL; + } + return __real_tsocket_address_string(addr, mem_ctx); +} + +static void test_json_add_int(_UNUSED_ void **state) +{ + struct json_object object; + int rc = 0; + + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + /* + * Test json integer failure + */ + will_return(__wrap_json_integer, true); + rc = json_add_int(&object, "name", 2); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + /* + * Test json object set new failure + */ + will_return(__wrap_json_integer, false); + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_int(&object, "name", 2); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + json_free(&object); +} + +static void test_json_add_bool(_UNUSED_ void **state) +{ + struct json_object object; + int rc = 0; + + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + /* + * json_boolean does not return an error code. + * Test json object set new failure + */ + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_bool(&object, "name", true); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); +} + +static void test_json_add_string(_UNUSED_ void **state) +{ + struct json_object object; + int rc = 0; + + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + /* + * Test json string failure + */ + will_return(__wrap_json_string, true); + rc = json_add_string(&object, "name", "value"); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + /* + * Test json object set new failure + */ + will_return(__wrap_json_string, false); + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_string(&object, "name", "value"); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + /* + * Test json object set new failure for a NULL string + */ + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_string(&object, "null", NULL); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); +} + +static void test_json_add_object(_UNUSED_ void **state) +{ + struct json_object object; + struct json_object value; + int rc = 0; + + will_return(__wrap_json_object, false); + will_return(__wrap_json_object, false); + + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + value = json_new_object(); + assert_false(json_is_invalid(&value)); + + /* + * Test json object set new failure + */ + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_object(&object, "name", &value); + + assert_false(json_is_invalid(&object)); + assert_false(json_is_invalid(&value)); + assert_int_equal(JSON_ERROR, rc); + + /* + * Test json object set new failure for a NULL value + */ + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_object(&object, "null", NULL); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); + json_free(&value); +} + +static void test_json_add_to_array(_UNUSED_ void **state) +{ + struct json_object array; + struct json_object value; + int rc = 0; + + will_return(__wrap_json_array, false); + will_return(__wrap_json_object, false); + + array = json_new_array(); + assert_false(json_is_invalid(&array)); + + value = json_new_object(); + assert_false(json_is_invalid(&value)); + + /* + * Test json array append new failure + */ + will_return(__wrap_json_array_append_new, JANSSON_FAILURE); + rc = json_add_object(&array, "name", &value); + + assert_false(json_is_invalid(&array)); + assert_false(json_is_invalid(&value)); + assert_int_equal(JSON_ERROR, rc); + + /* + * Test json append new failure with a NULL value + */ + will_return(__wrap_json_array_append_new, JANSSON_FAILURE); + rc = json_add_object(&array, "null", NULL); + + assert_false(json_is_invalid(&array)); + assert_int_equal(JSON_ERROR, rc); + + json_free(&array); + json_free(&value); +} + +static void test_json_add_timestamp(_UNUSED_ void **state) +{ + struct json_object object; + int rc = 0; + + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + /* + * Test json string failure + */ + will_return(__wrap_gettimeofday, 0); + will_return(__wrap_localtime, false); + will_return(__wrap_json_string, true); + rc = json_add_timestamp(&object); + + /* + * Test json_object_set_new failure + */ + will_return(__wrap_gettimeofday, 0); + will_return(__wrap_localtime, false); + will_return(__wrap_json_string, false); + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_timestamp(&object); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + /* + * Test gettimeofday failure + */ + will_return(__wrap_gettimeofday, -1); + rc = json_add_timestamp(&object); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + /* + * Test local time failure + */ + will_return(__wrap_gettimeofday, 0); + will_return(__wrap_localtime, true); + rc = json_add_timestamp(&object); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); +} + +static void test_json_add_stringn(_UNUSED_ void **state) +{ + struct json_object object; + int rc = 0; + + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + /* + * Test json string failure + */ + will_return(__wrap_json_stringn, true); + rc = json_add_stringn(&object, "name", "value", 3); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + /* + * Test json object set new failure + */ + will_return(__wrap_json_stringn, false); + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_stringn(&object, "name", "value", 3); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + /* + * Test json object set new failure for a NULL string + */ + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_stringn(&object, "null", NULL, 2); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + /* + * Test json object set new failure for a zero string size + */ + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_stringn(&object, "zero", "no value", 0); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + json_free(&object); +} + +static void test_json_add_version(_UNUSED_ void **state) +{ + struct json_object object; + int rc = 0; + + /* + * Fail creating the version object + */ + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + will_return(__wrap_json_object, true); + rc = json_add_version(&object, 1, 11); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); + + /* + * Fail adding the major version + */ + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + will_return(__wrap_json_object, false); + will_return(__wrap_json_integer, false); + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_version(&object, 2, 12); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); + + /* + * Fail adding the minor version + */ + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + will_return(__wrap_json_object, false); + 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, JANSSON_FAILURE); + rc = json_add_version(&object, 3, 13); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); + + /* + * Fail adding the version object + */ + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + will_return(__wrap_json_object, false); + 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, JANSSON_FAILURE); + rc = json_add_version(&object, 4, 14); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); +} + +static void test_json_add_address(_UNUSED_ void **state) +{ + struct json_object object; + int rc = 0; + struct tsocket_address *ip = NULL; + + TALLOC_CTX *ctx = NULL; + + will_return(__wrap_talloc_named_const, REAL_TALLOC); + ctx = talloc_new(NULL); + + /* + * Add a null address + */ + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_address(&object, "name", NULL); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + /* + * Add a non null address, json_object_set_new failure + */ + rc = tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 21, &ip); + assert_int_equal(0, rc); + + 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, JANSSON_FAILURE); + rc = json_add_address(&object, "name", ip); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + /* + * Add a non null address, with a talloc failure + */ + rc = tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 21, &ip); + assert_int_equal(0, rc); + + will_return(__wrap_talloc_named_const, NULL); + rc = json_add_address(&object, "name", ip); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + /* + * Add a non null address, tsocket_address_string failure + */ + rc = tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 21, &ip); + assert_int_equal(0, rc); + + will_return(__wrap_talloc_named_const, REAL_TALLOC); + will_return(__wrap_tsocket_address_string, true); + rc = json_add_address(&object, "name", ip); + + assert_false(json_is_invalid(&object)); + assert_int_equal(JSON_ERROR, rc); + + TALLOC_FREE(ctx); + json_free(&object); +} + +static void test_json_add_sid(void **state) +{ + struct json_object object; + const char *SID = "S-1-5-21-2470180966-3899876309-2637894779"; + struct dom_sid sid; + int rc; + + /* + * Add a null SID + */ + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_sid(&object, "null", NULL); + assert_int_equal(JSON_ERROR, rc); + + /* + * Add a non null SID + */ + assert_true(string_to_sid(&sid, SID)); + will_return(__wrap_json_string, false); + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_sid(&object, "sid", &sid); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); +} + +static void test_json_add_guid(void **state) +{ + struct json_object object; + const char *GUID = "3ab88633-1e57-4c1a-856c-d1bc4b15bbb1"; + struct GUID guid; + NTSTATUS status; + int rc; + + /* + * Add a null GUID + */ + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + will_return(__wrap_json_object_set_new, JANSSON_FAILURE); + rc = json_add_guid(&object, "null", NULL); + assert_int_equal(JSON_ERROR, rc); + + /* + * Add a non null GUID + */ + 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, JANSSON_FAILURE); + rc = json_add_guid(&object, "guid", &guid); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); +} + +static void test_json_to_string(_UNUSED_ void **state) +{ + struct json_object object; + char *s = NULL; + TALLOC_CTX *ctx = NULL; + + will_return(__wrap_talloc_named_const, REAL_TALLOC); + ctx = talloc_new(NULL); + + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + /* + * json_dumps failure + */ + will_return(__wrap_json_dumps, true); + s = json_to_string(ctx, &object); + assert_null(s); + + /* + * talloc failure + */ + will_return(__wrap_json_dumps, false); + will_return(__wrap_talloc_strdup, NULL); + s = json_to_string(ctx, &object); + assert_null(s); + TALLOC_FREE(ctx); + json_free(&object); +} + +static void test_json_get_array(_UNUSED_ void **state) +{ + struct json_object object; + struct json_object stored_array; + struct json_object array; + + int rc; + + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + will_return(__wrap_json_array, false); + stored_array = json_new_array(); + assert_false(json_is_invalid(&stored_array)); + + will_return(__wrap_json_object_set_new, CALL_ORIG); + rc = json_add_object(&object, "array", &stored_array); + assert_int_equal(0, rc); + + /* + * json array failure + */ + will_return(__wrap_json_array, true); + array = json_get_array(&object, "array"); + assert_true(json_is_invalid(&array)); + + /* + * json array extend failure + */ + will_return(__wrap_json_array, false); + will_return(__wrap_json_array_extend, true); + array = json_get_array(&object, "array"); + assert_true(json_is_invalid(&array)); + + json_free(&stored_array); + json_free(&object); +} + +static void test_json_get_object(_UNUSED_ void **state) +{ + struct json_object object; + struct json_object stored; + struct json_object retrieved; + + int rc; + + will_return(__wrap_json_object, false); + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + will_return(__wrap_json_object, false); + stored = json_new_object(); + assert_false(json_is_invalid(&stored)); + + will_return(__wrap_json_object_set_new, CALL_ORIG); + rc = json_add_object(&object, "stored", &stored); + assert_int_equal(0, rc); + + /* + * json object update failure + */ + will_return(__wrap_json_object, false); + will_return(__wrap_json_object_update, true); + retrieved = json_get_object(&object, "stored"); + assert_true(json_is_invalid(&retrieved)); + + json_free(&object); +} + +int main(_UNUSED_ int argc, _UNUSED_ const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_json_add_int), + cmocka_unit_test(test_json_add_bool), + cmocka_unit_test(test_json_add_string), + cmocka_unit_test(test_json_add_object), + cmocka_unit_test(test_json_add_to_array), + cmocka_unit_test(test_json_add_timestamp), + cmocka_unit_test(test_json_add_stringn), + 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_add_guid), + cmocka_unit_test(test_json_to_string), + cmocka_unit_test(test_json_get_array), + cmocka_unit_test(test_json_get_object), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/audit_logging/tests/audit_logging_test.c b/lib/audit_logging/tests/audit_logging_test.c new file mode 100644 index 0000000..0923882 --- /dev/null +++ b/lib/audit_logging/tests/audit_logging_test.c @@ -0,0 +1,919 @@ +/* + * Unit tests for the audit_logging library. + * + * Copyright (C) Andrew Bartlett 2018 + * + * 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 . + * + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ + +/* + * Note that the messaging routines (audit_message_send and get_event_server) + * are not tested by these unit tests. Currently they are for integration + * test support, and as such are exercised by the integration tests. + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "lib/util/talloc_stack.h" + +#include "lib/util/data_blob.h" +#include "lib/util/time.h" +#include "libcli/util/werror.h" +#include "lib/param/loadparm.h" +#include "libcli/security/dom_sid.h" +#include "librpc/ndr/libndr.h" + +#include "lib/audit_logging/audit_logging.h" + +static void test_json_add_int(_UNUSED_ void **state) +{ + struct json_object object; + struct json_t *value = NULL; + json_int_t m; + double n; + int rc = 0; + intmax_t big_int = ((intmax_t)1)<<33; + + object = json_new_object(); + assert_false(json_is_invalid(&object)); + rc = json_add_int(&object, "positive_one", 1); + assert_int_equal(0, rc); + rc = json_add_int(&object, "zero", 0); + assert_int_equal(0, rc); + rc = json_add_int(&object, "negative_one", -1); + assert_int_equal(0, rc); + rc = json_add_int(&object, "big_int", big_int); + assert_int_equal(0, rc); + + assert_int_equal(4, json_object_size(object.root)); + + value = json_object_get(object.root, "positive_one"); + assert_true(json_is_integer(value)); + n = json_number_value(value); + assert_true(n == 1.0); + + value = json_object_get(object.root, "zero"); + assert_true(json_is_integer(value)); + n = json_number_value(value); + assert_true(n == 0.0); + + value = json_object_get(object.root, "negative_one"); + assert_true(json_is_integer(value)); + n = json_number_value(value); + assert_true(n == -1.0); + + value = json_object_get(object.root, "big_int"); + assert_true(json_is_integer(value)); + m = json_integer_value(value); + assert_int_equal(m, big_int); + + object.valid = false; + rc = json_add_int(&object, "should fail 1", 0xf1); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); + + rc = json_add_int(&object, "should fail 2", 0xf2); + assert_int_equal(JSON_ERROR, rc); +} + +static void test_json_add_bool(_UNUSED_ void **state) +{ + struct json_object object; + struct json_t *value = NULL; + int rc = 0; + + object = json_new_object(); + assert_false(json_is_invalid(&object)); + rc = json_add_bool(&object, "true", true); + assert_int_equal(0, rc); + rc = json_add_bool(&object, "false", false); + assert_int_equal(0, rc); + + assert_int_equal(2, json_object_size(object.root)); + + value = json_object_get(object.root, "true"); + assert_true(json_is_boolean(value)); + assert_true(value == json_true()); + + value = json_object_get(object.root, "false"); + assert_true(json_is_boolean(value)); + assert_true(value == json_false()); + + object.valid = false; + rc = json_add_bool(&object, "should fail 1", true); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); + + rc = json_add_bool(&object, "should fail 2", false); + assert_int_equal(JSON_ERROR, rc); +} + +static void test_json_add_string(_UNUSED_ void **state) +{ + struct json_object object; + struct json_t *value = NULL; + const char *s = NULL; + int rc = 0; + + object = json_new_object(); + assert_false(json_is_invalid(&object)); + rc = json_add_string(&object, "null", NULL); + assert_int_equal(0, rc); + rc = json_add_string(&object, "empty", ""); + assert_int_equal(0, rc); + rc = json_add_string(&object, "name", "value"); + assert_int_equal(0, rc); + + assert_int_equal(3, json_object_size(object.root)); + + value = json_object_get(object.root, "null"); + assert_true(json_is_null(value)); + + value = json_object_get(object.root, "empty"); + assert_true(json_is_string(value)); + s = json_string_value(value); + assert_string_equal("", s); + + value = json_object_get(object.root, "name"); + assert_true(json_is_string(value)); + s = json_string_value(value); + assert_string_equal("value", s); + + object.valid = false; + rc = json_add_string(&object, "should fail 1", "A value"); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); + + rc = json_add_string(&object, "should fail 2", "Another value"); + assert_int_equal(JSON_ERROR, rc); +} + +static void test_json_add_object(_UNUSED_ void **state) +{ + struct json_object object; + struct json_object other; + struct json_object after; + struct json_object invalid = json_empty_object; + struct json_t *value = NULL; + int rc = 0; + + object = json_new_object(); + assert_false(json_is_invalid(&object)); + other = json_new_object(); + assert_false(json_is_invalid(&other)); + rc = json_add_object(&object, "null", NULL); + assert_int_equal(0, rc); + rc = json_add_object(&object, "other", &other); + assert_int_equal(0, rc); + + assert_int_equal(2, json_object_size(object.root)); + + value = json_object_get(object.root, "null"); + assert_true(json_is_null(value)); + + value = json_object_get(object.root, "other"); + assert_true(json_is_object(value)); + assert_ptr_equal(other.root, value); + + rc = json_add_object(&object, "invalid", &invalid); + assert_int_equal(JSON_ERROR, rc); + + object.valid = false; + after = json_new_object(); + assert_false(json_is_invalid(&after)); + rc = json_add_object(&object, "after", &after); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); + + rc = json_add_object(&object, "after", &after); + assert_int_equal(JSON_ERROR, rc); + + json_free(&after); +} + +static void test_json_add_to_array(_UNUSED_ void **state) +{ + struct json_object array; + struct json_object o1; + struct json_object o2; + struct json_object o3; + struct json_object after; + struct json_object invalid = json_empty_object; + struct json_t *value = NULL; + int rc = 0; + + array = json_new_array(); + assert_false(json_is_invalid(&array)); + assert_true(json_is_array(array.root)); + + o1 = json_new_object(); + assert_false(json_is_invalid(&o1)); + o2 = json_new_object(); + assert_false(json_is_invalid(&o2)); + o3 = json_new_object(); + assert_false(json_is_invalid(&o3)); + + rc = json_add_object(&array, NULL, &o3); + assert_int_equal(0, rc); + rc = json_add_object(&array, "", &o2); + assert_int_equal(0, rc); + rc = json_add_object(&array, "will-be-ignored", &o1); + assert_int_equal(0, rc); + rc = json_add_object(&array, NULL, NULL); + assert_int_equal(0, rc); + + assert_int_equal(4, json_array_size(array.root)); + + value = json_array_get(array.root, 0); + assert_ptr_equal(o3.root, value); + + value = json_array_get(array.root, 1); + assert_ptr_equal(o2.root, value); + + value = json_array_get(array.root, 2); + assert_ptr_equal(o1.root, value); + + value = json_array_get(array.root, 3); + assert_true(json_is_null(value)); + + rc = json_add_object(&array, "invalid", &invalid); + assert_int_equal(JSON_ERROR, rc); + + array.valid = false; + after = json_new_object(); + assert_false(json_is_invalid(&after)); + rc = json_add_object(&array, "after", &after); + assert_int_equal(JSON_ERROR, rc); + + json_free(&array); + + rc = json_add_object(&array, "after", &after); + assert_int_equal(JSON_ERROR, rc); + + json_free(&after); +} + +static void test_json_add_timestamp(_UNUSED_ void **state) +{ + struct json_object object; + struct json_t *ts = NULL; + const char *t = NULL; + int rc; + int usec, tz; + char c[2]; + struct tm tm; + time_t before; + time_t after; + time_t actual; + struct timeval tv; + int ret; + + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + ret = gettimeofday(&tv, NULL); + assert_int_equal(0, ret); + before = tv.tv_sec; + + rc = json_add_timestamp(&object); + assert_int_equal(0, rc); + + ret = gettimeofday(&tv, NULL); + assert_int_equal(0, ret); + after = tv.tv_sec; + + ts = json_object_get(object.root, "timestamp"); + assert_true(json_is_string(ts)); + + /* + * Convert the returned ISO 8601 timestamp into a time_t + * Note for convenience we ignore the value of the microsecond + * part of the time stamp. + */ + t = json_string_value(ts); + rc = sscanf( + t, + "%4d-%2d-%2dT%2d:%2d:%2d.%6d%1c%4d", + &tm.tm_year, + &tm.tm_mon, + &tm.tm_mday, + &tm.tm_hour, + &tm.tm_min, + &tm.tm_sec, + &usec, + c, + &tz); + assert_int_equal(9, rc); + tm.tm_year = tm.tm_year - 1900; + tm.tm_mon = tm.tm_mon - 1; + tm.tm_isdst = -1; + actual = mktime(&tm); + + /* + * The timestamp should be before <= actual <= after + */ + assert_true(difftime(actual, before) >= 0); + assert_true(difftime(after, actual) >= 0); + + object.valid = false; + rc = json_add_timestamp(&object); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); + + rc = json_add_timestamp(&object); + assert_int_equal(JSON_ERROR, rc); +} + +static void test_json_add_stringn(_UNUSED_ void **state) +{ + struct json_object object; + struct json_t *value = NULL; + const char *s = NULL; + int rc = 0; + + object = json_new_object(); + assert_false(json_is_invalid(&object)); + rc = json_add_stringn(&object, "null", NULL, 10); + assert_int_equal(0, rc); + rc = json_add_stringn(&object, "null-zero-len", NULL, 0); + assert_int_equal(0, rc); + rc = json_add_stringn(&object, "empty", "", 1); + assert_int_equal(0, rc); + rc = json_add_stringn(&object, "empty-zero-len", "", 0); + assert_int_equal(0, rc); + rc = json_add_stringn(&object, "value-less-than-len", "123456", 7); + assert_int_equal(0, rc); + rc = json_add_stringn(&object, "value-greater-than-len", "abcd", 3); + assert_int_equal(0, rc); + rc = json_add_stringn(&object, "value-equal-len", "ZYX", 3); + assert_int_equal(0, rc); + rc = json_add_stringn( + &object, "value-len-is-zero", "this will be null", 0); + assert_int_equal(0, rc); + + assert_int_equal(8, json_object_size(object.root)); + + value = json_object_get(object.root, "null"); + assert_true(json_is_null(value)); + + value = json_object_get(object.root, "null-zero-len"); + assert_true(json_is_null(value)); + + value = json_object_get(object.root, "empty"); + assert_true(json_is_string(value)); + s = json_string_value(value); + assert_string_equal("", s); + + value = json_object_get(object.root, "empty-zero-len"); + assert_true(json_is_null(value)); + + value = json_object_get(object.root, "value-greater-than-len"); + assert_true(json_is_string(value)); + s = json_string_value(value); + assert_string_equal("abc", s); + assert_int_equal(3, strlen(s)); + + value = json_object_get(object.root, "value-equal-len"); + assert_true(json_is_string(value)); + s = json_string_value(value); + assert_string_equal("ZYX", s); + assert_int_equal(3, strlen(s)); + + value = json_object_get(object.root, "value-len-is-zero"); + assert_true(json_is_null(value)); + + object.valid = false; + rc = json_add_stringn(&object, "fail-01", "xxxxxxx", 1); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); + + rc = json_add_stringn(&object, "fail-02", "xxxxxxx", 1); + assert_int_equal(JSON_ERROR, rc); +} + +static void test_json_add_version(_UNUSED_ void **state) +{ + struct json_object object; + struct json_t *version = NULL; + struct json_t *v = NULL; + double n; + int rc; + + object = json_new_object(); + assert_false(json_is_invalid(&object)); + rc = json_add_version(&object, 3, 1); + assert_int_equal(0, rc); + + assert_int_equal(1, json_object_size(object.root)); + + version = json_object_get(object.root, "version"); + assert_true(json_is_object(version)); + assert_int_equal(2, json_object_size(version)); + + v = json_object_get(version, "major"); + assert_true(json_is_integer(v)); + n = json_number_value(v); + assert_true(n == 3.0); + + v = json_object_get(version, "minor"); + assert_true(json_is_integer(v)); + n = json_number_value(v); + assert_true(n == 1.0); + + object.valid = false; + rc = json_add_version(&object, 3, 1); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); + + rc = json_add_version(&object, 3, 1); + assert_int_equal(JSON_ERROR, rc); +} + +static void test_json_add_address(_UNUSED_ void **state) +{ + struct json_object object; + struct json_t *value = NULL; + struct tsocket_address *ip4 = NULL; + struct tsocket_address *ip6 = NULL; + struct tsocket_address *pipe = NULL; + + struct tsocket_address *after = NULL; + const char *s = NULL; + int rc; + + TALLOC_CTX *ctx = talloc_new(NULL); + + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + rc = json_add_address(&object, "null", NULL); + assert_int_equal(0, rc); + + rc = tsocket_address_inet_from_strings( + ctx, + "ip", + "127.0.0.1", + 21, + &ip4); + assert_int_equal(0, rc); + rc = json_add_address(&object, "ip4", ip4); + assert_int_equal(0, rc); + + rc = tsocket_address_inet_from_strings( + ctx, + "ip", + "2001:db8:0:0:1:0:0:1", + 42, + &ip6); + assert_int_equal(0, rc); + rc = json_add_address(&object, "ip6", ip6); + assert_int_equal(0, rc); + + rc = tsocket_address_unix_from_path(ctx, "/samba/pipe", &pipe); + assert_int_equal(0, rc); + rc = json_add_address(&object, "pipe", pipe); + assert_int_equal(0, rc); + + assert_int_equal(4, json_object_size(object.root)); + + value = json_object_get(object.root, "null"); + assert_true(json_is_null(value)); + + value = json_object_get(object.root, "ip4"); + assert_true(json_is_string(value)); + s = json_string_value(value); + assert_string_equal("ipv4:127.0.0.1:21", s); + + value = json_object_get(object.root, "ip6"); + assert_true(json_is_string(value)); + s = json_string_value(value); + assert_string_equal("ipv6:2001:db8::1:0:0:1:42", s); + + value = json_object_get(object.root, "pipe"); + assert_true(json_is_string(value)); + s = json_string_value(value); + assert_string_equal("unix:/samba/pipe", s); + + object.valid = false; + rc = tsocket_address_inet_from_strings( + ctx, "ip", "127.0.0.11", 23, &after); + assert_int_equal(0, rc); + rc = json_add_address(&object, "invalid_object", after); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); + + rc = json_add_address(&object, "freed object", after); + assert_int_equal(JSON_ERROR, rc); + + TALLOC_FREE(ctx); +} + +static void test_json_add_sid(_UNUSED_ void **state) +{ + struct json_object object; + struct json_t *value = NULL; + const char *SID = "S-1-5-21-2470180966-3899876309-2637894779"; + struct dom_sid sid; + const char *s = NULL; + int rc; + + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + rc = json_add_sid(&object, "null", NULL); + assert_int_equal(0, rc); + + assert_true(string_to_sid(&sid, SID)); + rc = json_add_sid(&object, "sid", &sid); + assert_int_equal(0, rc); + + assert_int_equal(2, json_object_size(object.root)); + + value = json_object_get(object.root, "null"); + assert_true(json_is_null(value)); + + value = json_object_get(object.root, "sid"); + assert_true(json_is_string(value)); + s = json_string_value(value); + assert_string_equal(SID, s); + + object.valid = false; + rc = json_add_sid(&object, "invalid_object", &sid); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); + + rc = json_add_sid(&object, "freed_object", &sid); + assert_int_equal(JSON_ERROR, rc); +} + +static void test_json_add_guid(_UNUSED_ void **state) +{ + struct json_object object; + struct json_t *value = NULL; + const char *GUID = "3ab88633-1e57-4c1a-856c-d1bc4b15bbb1"; + struct GUID guid; + const char *s = NULL; + NTSTATUS status; + int rc; + + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + rc = json_add_guid(&object, "null", NULL); + assert_int_equal(0, rc); + + status = GUID_from_string(GUID, &guid); + assert_true(NT_STATUS_IS_OK(status)); + rc = json_add_guid(&object, "guid", &guid); + assert_int_equal(0, rc); + + assert_int_equal(2, json_object_size(object.root)); + + value = json_object_get(object.root, "null"); + assert_true(json_is_null(value)); + + value = json_object_get(object.root, "guid"); + assert_true(json_is_string(value)); + s = json_string_value(value); + assert_string_equal(GUID, s); + + object.valid = false; + rc = json_add_guid(&object, "invalid_object", &guid); + assert_int_equal(JSON_ERROR, rc); + + json_free(&object); + + rc = json_add_guid(&object, "freed_object", &guid); + assert_int_equal(JSON_ERROR, rc); +} + +static void test_json_to_string(_UNUSED_ void **state) +{ + struct json_object object; + char *s = NULL; + int rc; + + TALLOC_CTX *ctx = talloc_new(NULL); + + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + s = json_to_string(ctx, &object); + assert_string_equal("{}", s); + TALLOC_FREE(s); + + rc = json_add_string(&object, "name", "value"); + assert_int_equal(0, rc); + s = json_to_string(ctx, &object); + assert_string_equal("{\"name\": \"value\"}", s); + TALLOC_FREE(s); + + object.valid = false; + s = json_to_string(ctx, &object); + assert_null(s); + + json_free(&object); + + object.valid = true; + object.root = NULL; + + s = json_to_string(ctx, &object); + assert_null(s); + TALLOC_FREE(ctx); +} + +static void test_json_get_array(_UNUSED_ void **state) +{ + struct json_object object; + struct json_object array; + struct json_object stored_array = json_new_array(); + json_t *value = NULL; + json_t *o = NULL; + struct json_object o1; + struct json_object o2; + int rc; + + assert_false(json_is_invalid(&stored_array)); + + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + array = json_get_array(&object, "not-there"); + assert_true(array.valid); + assert_non_null(array.root); + assert_true(json_is_array(array.root)); + json_free(&array); + + o1 = json_new_object(); + assert_false(json_is_invalid(&o1)); + rc = json_add_string(&o1, "value", "value-one"); + assert_int_equal(0, rc); + rc = json_add_object(&stored_array, NULL, &o1); + assert_int_equal(0, rc); + rc = json_add_object(&object, "stored_array", &stored_array); + assert_int_equal(0, rc); + + array = json_get_array(&object, "stored_array"); + assert_true(array.valid); + assert_non_null(array.root); + assert_true(json_is_array(array.root)); + + assert_int_equal(1, json_array_size(array.root)); + + o = json_array_get(array.root, 0); + assert_non_null(o); + assert_true(json_is_object(o)); + + value = json_object_get(o, "value"); + assert_non_null(value); + assert_true(json_is_string(value)); + + assert_string_equal("value-one", json_string_value(value)); + json_free(&array); + + /* + * Now update the array and add it back to the object + */ + array = json_get_array(&object, "stored_array"); + assert_true(json_is_array(array.root)); + o2 = json_new_object(); + assert_false(json_is_invalid(&o2)); + rc = json_add_string(&o2, "value", "value-two"); + assert_int_equal(0, rc); + assert_true(o2.valid); + rc = json_add_object(&array, NULL, &o2); + assert_int_equal(0, rc); + assert_true(json_is_array(array.root)); + rc = json_add_object(&object, "stored_array", &array); + assert_int_equal(0, rc); + assert_true(json_is_array(array.root)); + + array = json_get_array(&object, "stored_array"); + assert_non_null(array.root); + assert_true(json_is_array(array.root)); + assert_true(array.valid); + assert_true(json_is_array(array.root)); + + assert_int_equal(2, json_array_size(array.root)); + + o = json_array_get(array.root, 0); + assert_non_null(o); + assert_true(json_is_object(o)); + + assert_non_null(value); + assert_true(json_is_string(value)); + + assert_string_equal("value-one", json_string_value(value)); + + o = json_array_get(array.root, 1); + assert_non_null(o); + assert_true(json_is_object(o)); + + value = json_object_get(o, "value"); + assert_non_null(value); + assert_true(json_is_string(value)); + + assert_string_equal("value-two", json_string_value(value)); + + json_free(&array); + json_free(&object); + + array = json_get_array(&object, "stored_array"); + assert_false(array.valid); + json_free(&array); +} + +static void test_json_get_object(_UNUSED_ void **state) +{ + struct json_object object; + struct json_object o1; + struct json_object o2; + struct json_object o3; + json_t *value = NULL; + int rc; + + object = json_new_object(); + assert_false(json_is_invalid(&object)); + + o1 = json_get_object(&object, "not-there"); + assert_true(o1.valid); + assert_non_null(o1.root); + assert_true(json_is_object(o1.root)); + json_free(&o1); + + o1 = json_new_object(); + assert_false(json_is_invalid(&o1)); + rc = json_add_string(&o1, "value", "value-one"); + assert_int_equal(0, rc); + rc = json_add_object(&object, "stored_object", &o1); + assert_int_equal(0, rc); + + o2 = json_get_object(&object, "stored_object"); + assert_true(o2.valid); + assert_non_null(o2.root); + assert_true(json_is_object(o2.root)); + + value = json_object_get(o2.root, "value"); + assert_non_null(value); + assert_true(json_is_string(value)); + + assert_string_equal("value-one", json_string_value(value)); + + rc = json_add_string(&o2, "value", "value-two"); + assert_int_equal(0, rc); + rc = json_add_object(&object, "stored_object", &o2); + assert_int_equal(0, rc); + + o3 = json_get_object(&object, "stored_object"); + assert_true(o3.valid); + assert_non_null(o3.root); + assert_true(json_is_object(o3.root)); + + value = json_object_get(o3.root, "value"); + assert_non_null(value); + assert_true(json_is_string(value)); + + assert_string_equal("value-two", json_string_value(value)); + + json_free(&o3); + json_free(&object); + + o3 = json_get_object(&object, "stored_object"); + assert_false(o3.valid); + json_free(&o3); +} + +static void test_audit_get_timestamp(_UNUSED_ void **state) +{ + const char *t = NULL; + char *c; + struct tm tm = {}; + time_t before; + time_t after; + time_t actual; + struct timeval tv; + int ret; + char *env_tz = NULL; + char *orig_tz = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + /* + * Explicitly set the time zone to UTC to make the test easier + */ + env_tz = getenv("TZ"); + if (env_tz != NULL) { + orig_tz = talloc_strdup(ctx, env_tz); + } + setenv("TZ", "UTC", 1); + + ret = gettimeofday(&tv, NULL); + assert_int_equal(0, ret); + before = tv.tv_sec; + + t = audit_get_timestamp(ctx); + + ret = gettimeofday(&tv, NULL); + assert_int_equal(0, ret); + after = tv.tv_sec; + + c = strptime(t, "%a, %d %b %Y %H:%M:%S", &tm); + + /* + * Restore the time zone if we changed it + */ + if (orig_tz != NULL) { + setenv("TZ", orig_tz, 1); + TALLOC_FREE(orig_tz); + } + + assert_non_null(c); + tm.tm_isdst = -1; + if (c != NULL && *c == '.') { + char *e; + strtod(c, &e); + c = e; + } + if (c != NULL && *c == ' ') { + assert_string_equal(" UTC", c); + c += 4; + } + assert_int_equal(0, strlen(c)); + + actual = mktime(&tm); + + /* + * The timestamp should be before <= actual <= after + */ + assert_true(difftime(actual, before) >= 0); + assert_true(difftime(after, actual) >= 0); + + TALLOC_FREE(ctx); +} + +int main(_UNUSED_ int argc, _UNUSED_ const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_json_add_int), + cmocka_unit_test(test_json_add_bool), + cmocka_unit_test(test_json_add_string), + cmocka_unit_test(test_json_add_object), + cmocka_unit_test(test_json_add_to_array), + cmocka_unit_test(test_json_add_timestamp), + cmocka_unit_test(test_json_add_stringn), + 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_add_guid), + cmocka_unit_test(test_json_to_string), + cmocka_unit_test(test_json_get_array), + cmocka_unit_test(test_json_get_object), + cmocka_unit_test(test_audit_get_timestamp), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/lib/audit_logging/wscript_build b/lib/audit_logging/wscript_build new file mode 100644 index 0000000..90c48ee --- /dev/null +++ b/lib/audit_logging/wscript_build @@ -0,0 +1,60 @@ +#!/usr/bin/env python + +bld.SAMBA_SUBSYSTEM( + 'audit_logging', + deps='''MESSAGING_SEND + jansson + samba-debug + LIBTSOCKET + authn_policy''', + source='audit_logging.c' +) + +if bld.AD_DC_BUILD_IS_ENABLED(): + bld.SAMBA_BINARY( + 'audit_logging_test', + source='tests/audit_logging_test.c', + deps=''' + audit_logging + jansson + cmocka + talloc + samba-util + LIBTSOCKET + authn_policy + ''', + for_selftest=True + ) + +if bld.AD_DC_BUILD_IS_ENABLED(): + bld.SAMBA_BINARY( + 'audit_logging_error_test', + source='tests/audit_logging_error_test.c', + deps=''' + audit_logging + jansson + cmocka + talloc + samba-util + LIBTSOCKET + authn_policy + ''', + for_selftest=True, + ldflags=''' + -Wl,--wrap,json_object_set_new + -Wl,--wrap,json_object_update + -Wl,--wrap,json_array_append_new + -Wl,--wrap,json_array_extend + -Wl,--wrap,json_object + -Wl,--wrap,json_string + -Wl,--wrap,json_stringn + -Wl,--wrap,json_integer + -Wl,--wrap,json_array + -Wl,--wrap,json_dumps + -Wl,--wrap,gettimeofday + -Wl,--wrap,localtime + -Wl,--wrap,talloc_named_const + -Wl,--wrap,talloc_strdup + -Wl,--wrap,tsocket_address_string + ''' + ) -- cgit v1.2.3