auth logging: Extract common audit logging code
authorGary Lockyer <gary@catalyst.net.nz>
Mon, 9 Apr 2018 18:45:47 +0000 (06:45 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Wed, 16 May 2018 02:07:16 +0000 (04:07 +0200)
Extract the common audit logging code into a library to allow it's
re-use in other logging modules.

Signed-off-by: Gary Lockyer <gary@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
lib/audit_logging/audit_logging.c [new file with mode: 0644]
lib/audit_logging/audit_logging.h [new file with mode: 0644]
lib/audit_logging/tests/audit_logging_test.c [new file with mode: 0644]
lib/audit_logging/wscript_build [new file with mode: 0644]
source4/selftest/tests.py
wscript_build

diff --git a/lib/audit_logging/audit_logging.c b/lib/audit_logging/audit_logging.c
new file mode 100644 (file)
index 0000000..7da161c
--- /dev/null
@@ -0,0 +1,771 @@
+/*
+   common routines for audit logging
+
+   Copyright (C) Andrew Bartlett <abartlet@samba.org> 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 <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Error handling:
+ *
+ * The json_object structure contains a boolean 'error'.  This is set whenever
+ * an error is detected. All the library functions check this flag and return
+ * immediately if it is set.
+ *
+ *     if (object->error) {
+ *             return;
+ *     }
+ *
+ * This allows the operations to be sequenced naturally with out the clutter
+ * of error status checks.
+ *
+ *     audit = json_new_object();
+ *     json_add_version(&audit, OPERATION_MAJOR, OPERATION_MINOR);
+ *     json_add_int(&audit, "statusCode", ret);
+ *     json_add_string(&audit, "status", ldb_strerror(ret));
+ *     json_add_string(&audit, "operation", operation);
+ *     json_add_address(&audit, "remoteAddress", remote);
+ *     json_add_sid(&audit, "userSid", sid);
+ *     json_add_string(&audit, "dn", dn);
+ *     json_add_guid(&audit, "transactionId", &ac->transaction_guid);
+ *     json_add_guid(&audit, "sessionId", unique_session_token);
+ *
+ * The assumptions are that errors will be rare, and that the audit logging
+ * code should not cause failures. So errors are logged but processing
+ * continues on a best effort basis.
+ */
+
+#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 "audit_logging.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.
+ *
+ */
+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 r;                  /* response code from gettimeofday       */
+       char * ts;              /* formatted time stamp                  */
+
+       r = gettimeofday(&tv, NULL);
+       if (r) {
+               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, tv.tv_usec, tz);
+       if (ts == NULL) {
+               DBG_ERR("Out of memory formatting time stamp\n");
+       }
+       return ts;
+}
+
+#ifdef HAVE_JANSSON
+
+#include "system/time.h"
+
+/*
+ * @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_NOTICE(
+                       "Failed to find '%s' registered on the message bus to "
+                       "send 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 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,
+       const char *message)
+{
+       struct server_id event_server;
+       NTSTATUS status;
+       DATA_BLOB message_blob = data_blob_string_const(message);
+
+       if (msg_ctx == NULL) {
+               DBG_DEBUG("No messaging 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) &&
+           !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+               DBG_ERR("get_event_server for %s returned (%s)\n",
+                       server_name,
+                       nt_errstr(status));
+               return;
+       }
+
+       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) &&
+                   !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+                       DBG_ERR("get_event_server for %s returned (%s)\n",
+                               server_name,
+                               nt_errstr(status));
+                       return;
+               }
+               imessaging_send(
+                       msg_ctx,
+                       event_server,
+                       message_type,
+                       &message_blob);
+       }
+}
+
+/*
+ * @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 inplementation
+ * allocates memory with malloc and not talloc.
+ *
+ * @return a struct json_object, error will be set to true if the object
+ *         could not be created.
+ *
+ */
+struct json_object json_new_object(void) {
+
+       struct json_object object;
+       object.error = false;
+
+       object.root = json_object();
+       if (object.root == NULL) {
+               object.error = true;
+               DBG_ERR("Unable to create json_object\n");
+       }
+       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 inplementation
+ * 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;
+       array.error = false;
+
+       array.root = json_array();
+       if (array.root == NULL) {
+               array.error = true;
+               DBG_ERR("Unable to create json_array\n");
+       }
+       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->error = true;
+}
+
+/*
+ * @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(struct json_object *object)
+{
+       return object->error;
+}
+
+/*
+ * @brief Add an integer value to a JSON object.
+ *
+ * Add an integer value named 'name' to the json object.
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name of the value.
+ * @param value the value.
+ *
+ */
+void json_add_int(struct json_object *object,
+                 const char* name,
+                 const int value)
+{
+       int rc = 0;
+
+       if (object->error) {
+               return;
+       }
+
+       rc = json_object_set_new(object->root, name, json_integer(value));
+       if (rc) {
+               DBG_ERR("Unable to set name [%s] value [%d]\n", name, value);
+               object->error = true;
+       }
+}
+
+/*
+ * @brief Add a boolean value to a JSON object.
+ *
+ * Add a boolean value named 'name' to the json object.
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param value the value.
+ *
+ */
+void json_add_bool(struct json_object *object,
+                  const char* name,
+                  const bool value)
+{
+       int rc = 0;
+
+       if (object->error) {
+               return;
+       }
+
+       rc = json_object_set_new(object->root, name, json_boolean(value));
+       if (rc) {
+               DBG_ERR("Unable to set name [%s] value [%d]\n", name, value);
+               object->error = true;
+       }
+
+}
+
+/*
+ * @brief Add a string value to a JSON object.
+ *
+ * Add a string value named 'name' to the json object.
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param value the value.
+ *
+ */
+void json_add_string(struct json_object *object,
+                    const char* name,
+                    const char* value)
+{
+       int rc = 0;
+
+       if (object->error) {
+               return;
+       }
+
+       if (value) {
+               rc = json_object_set_new(
+                       object->root,
+                       name,
+                       json_string(value));
+       } else {
+               rc = json_object_set_new(object->root, name, json_null());
+       }
+       if (rc) {
+               DBG_ERR("Unable to set name [%s] value [%s]\n", name, value);
+               object->error = true;
+       }
+}
+
+/*
+ * @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 (array->error) {
+               return;
+       }
+
+       if (json_is_array(array->root) == false) {
+               DBG_ERR("JSON object is not an array\n");
+               array->error = true;
+               return;
+       }
+}
+
+/*
+ * @brief Add a JSON object to a JSON object.
+ *
+ * Add a JSON object named 'name' to the json object.
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param value the value.
+ *
+ */
+void json_add_object(struct json_object *object,
+               const char* name,
+               struct json_object *value)
+{
+       int rc = 0;
+       json_t *jv = NULL;
+
+       if (object->error) {
+               return;
+       }
+
+       if (value != NULL && value->error) {
+               object->error = true;
+               return;
+       }
+
+       jv = value == NULL ? json_null() : value->root;
+
+       if (json_is_array(object->root)) {
+               rc = json_array_append_new(object->root, jv);
+       } else if (json_is_object(object->root)) {
+               rc = json_object_set_new(object->root, name,  jv);
+       } else {
+               DBG_ERR("Invalid JSON object type\n");
+               object->error = true;
+       }
+       if (rc) {
+               DBG_ERR("Unable to add object [%s]\n", name);
+               object->error = true;
+       }
+}
+
+/*
+ * @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.
+ *
+ * In the event of an error object will be invalidated.
+ *
+ * @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.
+ *
+ */
+void json_add_stringn(
+       struct json_object *object,
+       const char *name,
+       const char *value,
+       const size_t len)
+{
+
+       int rc = 0;
+       if (object->error) {
+               return;
+       }
+
+       if (value != NULL && len > 0) {
+               char buffer[len+1];
+               strncpy(buffer, value, len);
+               buffer[len] = '\0';
+               rc = json_object_set_new(object->root,
+                                        name,
+                                        json_string(buffer));
+       } else {
+               rc = json_object_set_new(object->root, name, json_null());
+       }
+       if (rc) {
+               DBG_ERR("Unable to set name [%s] value [%s]\n", name, value);
+               object->error = true;
+       }
+}
+
+/*
+ * @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.
+ *
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param major the major version number
+ * @param minor the minor version number
+ */
+void json_add_version(struct json_object *object, int major, int minor)
+{
+       struct json_object version = json_new_object();
+       json_add_int(&version, "major", major);
+       json_add_int(&version, "minor", minor);
+       json_add_object(object, "version", &version);
+}
+
+/*
+ * @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"
+ *
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ */
+void json_add_timestamp(struct json_object *object)
+{
+       char buffer[40];        /* formatted time less usec and timezone */
+       char timestamp[50];     /* the formatted ISO 8601 time stamp     */
+       char tz[10];            /* formatted time zone                   */
+       struct tm* tm_info;     /* current local time                    */
+       struct timeval tv;      /* current system time                   */
+       int r;                  /* response code from gettimeofday       */
+
+       if (object->error) {
+               return;
+       }
+
+       r = gettimeofday(&tv, NULL);
+       if (r) {
+               DBG_ERR("Unable to get time of day: (%d) %s\n",
+                       errno,
+                       strerror(errno));
+               object->error = true;
+               return;
+       }
+
+       tm_info = localtime(&tv.tv_sec);
+       if (tm_info == NULL) {
+               DBG_ERR("Unable to determine local time\n");
+               object->error = true;
+               return;
+       }
+
+       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);
+       json_add_string(object, "timestamp", timestamp);
+}
+
+
+/*
+ *@brief Add a tsocket_address to a JSON object
+ *
+ * Add the string representation of a Samba tsocket_address to the object.
+ *
+ * "localAddress":"ipv6::::0"
+ *
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param address the tsocket_address.
+ *
+ */
+void json_add_address(
+       struct json_object *object,
+       const char *name,
+       const struct tsocket_address *address)
+{
+
+       if (object->error) {
+               return;
+       }
+       if (address == NULL) {
+               int rc = json_object_set_new(object->root, name, json_null());
+               if (rc) {
+                       DBG_ERR("Unable to set address [%s] to null\n", name);
+                       object->error = true;
+               }
+       } else {
+               TALLOC_CTX *ctx = talloc_new(NULL);
+               char *s = NULL;
+
+               s = tsocket_address_string(address, ctx);
+               json_add_string(object, name, s);
+               TALLOC_FREE(ctx);
+       }
+}
+
+/*
+ * @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"
+ *
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param sid the sid
+ *
+ */
+void json_add_sid(
+       struct json_object *object,
+       const char *name,
+       const struct dom_sid *sid)
+{
+
+       if (object->error) {
+               return;
+       }
+       if (sid == NULL) {
+               int rc = json_object_set_new(object->root, name, json_null());
+               if (rc) {
+                       DBG_ERR("Unable to set SID [%s] to null\n", name);
+                       object->error = true;
+               }
+       } else {
+               char sid_buf[DOM_SID_STR_BUFLEN];
+
+               dom_sid_string_buf(sid, sid_buf, sizeof(sid_buf));
+               json_add_string(object, name, sid_buf);
+       }
+}
+
+/*
+ * @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"
+ *
+ * In the event of an error object will be invalidated.
+ *
+ * @param object the JSON object to be updated.
+ * @param name the name.
+ * @param guid the guid.
+ *
+ *
+ */
+void json_add_guid(
+       struct json_object *object,
+       const char *name,
+       const struct GUID *guid)
+{
+
+
+       if (object->error) {
+               return;
+       }
+       if (guid == NULL) {
+               int rc = json_object_set_new(object->root, name, json_null());
+               if (rc) {
+                       DBG_ERR("Unable to set GUID [%s] to null\n", name);
+                       object->error = true;
+               }
+       } else {
+               char *guid_str;
+               struct GUID_txt_buf guid_buff;
+
+               guid_str = GUID_buf_string(guid, &guid_buff);
+               json_add_string(object, name, guid_str);
+       }
+}
+
+
+/*
+ * @brief Convert a JSON object into a string
+ *
+ * Convert the jsom object into a string suitable for printing on a log line,
+ * i.e. with no embedded line breaks.
+ *
+ * If the object is invalid it 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, struct json_object *object)
+{
+       char *json = NULL;
+       char *json_string = NULL;
+
+       if (object->error) {
+               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;
+}
+#endif
diff --git a/lib/audit_logging/audit_logging.h b/lib/audit_logging/audit_logging.h
new file mode 100644 (file)
index 0000000..763f3ed
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+   common routines for audit logging
+
+   Copyright (C) Andrew Bartlett <abartlet@samba.org> 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <talloc.h>
+#include "lib/messaging/irpc.h"
+#include "lib/tsocket/tsocket.h"
+
+char* audit_get_timestamp(
+       TALLOC_CTX *frame);
+
+void audit_message_send(
+       struct imessaging_context *msg_ctx,
+       const char *server_name,
+       uint32_t message_type,
+       const char *message);
+#ifdef HAVE_JANSSON
+#include <jansson.h>
+/*
+ * Wrapper for jannson JSON object
+ *
+ */
+struct json_object {
+       json_t *root;
+       bool error;
+};
+
+struct json_object json_new_object(void);
+struct json_object json_new_array(void);
+void json_free(struct json_object *object);
+void json_assert_is_array(struct json_object *array);
+bool json_is_invalid(struct json_object *object);
+
+void json_add_int(
+       struct json_object *object,
+       const char* name,
+       const int value);
+void json_add_bool(
+       struct json_object *object,
+       const char* name,
+       const bool value);
+void json_add_string(
+       struct json_object *object,
+       const char* name,
+       const char* value);
+void json_add_object(
+       struct json_object *object,
+       const char* name,
+       struct json_object *value);
+void json_add_stringn(
+       struct json_object *object,
+       const char *name,
+       const char *value,
+       const size_t len);
+void json_add_version(
+       struct json_object *object,
+       int major,
+       int minor);
+void json_add_timestamp(struct json_object *object);
+void json_add_address(
+       struct json_object *object,
+       const char *name,
+       const struct tsocket_address *address);
+void json_add_sid(
+       struct json_object *object,
+       const char *name,
+       const struct dom_sid *sid);
+void json_add_guid(
+       struct json_object *object,
+       const char *name,
+       const struct GUID *guid);
+
+char *json_to_string(TALLOC_CTX *mem_ctx, struct json_object *object);
+#endif
diff --git a/lib/audit_logging/tests/audit_logging_test.c b/lib/audit_logging/tests/audit_logging_test.c
new file mode 100644 (file)
index 0000000..9ba71fb
--- /dev/null
@@ -0,0 +1,557 @@
+/*
+ * Unit tests for the audit_logging library.
+ *
+ *  Copyright (C) Andrew Bartlett <abartlet@samba.org> 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * 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 <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <string.h>
+#include <time.h>
+#include <tevent.h>
+#include <config.h>
+#include <talloc.h>
+#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"
+
+#ifdef HAVE_JANSSON
+static void test_json_add_int(void **state)
+{
+       struct json_object object;
+       struct json_t *value = NULL;
+       double n;
+
+       object = json_new_object();
+       json_add_int(&object, "positive_one", 1);
+       json_add_int(&object, "zero", 0);
+       json_add_int(&object, "negative_one", -1);
+
+
+       assert_int_equal(3, 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);
+
+       json_free(&object);
+}
+
+static void test_json_add_bool(void **state)
+{
+       struct json_object object;
+       struct json_t *value = NULL;
+
+       object = json_new_object();
+       json_add_bool(&object, "true", true);
+       json_add_bool(&object, "false", false);
+
+
+       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());
+
+       json_free(&object);
+}
+
+static void test_json_add_string(void **state)
+{
+       struct json_object object;
+       struct json_t *value = NULL;
+       const char *s = NULL;
+
+       object = json_new_object();
+       json_add_string(&object, "null", NULL);
+       json_add_string(&object, "empty", "");
+       json_add_string(&object, "name", "value");
+
+
+
+       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);
+       json_free(&object);
+}
+
+static void test_json_add_object(void **state)
+{
+       struct json_object object;
+       struct json_object other;
+       struct json_t *value = NULL;
+
+       object = json_new_object();
+       other  = json_new_object();
+       json_add_object(&object, "null", NULL);
+       json_add_object(&object, "other", &other);
+
+
+
+       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);
+
+       json_free(&object);
+}
+
+static void test_json_add_to_array(void **state)
+{
+       struct json_object array;
+       struct json_object o1;
+       struct json_object o2;
+       struct json_object o3;
+       struct json_t *value = NULL;
+
+       array = json_new_array();
+       assert_true(json_is_array(array.root));
+
+       o1 = json_new_object();
+       o2 = json_new_object();
+       o3 = json_new_object();
+
+       json_add_object(&array, NULL, &o3);
+       json_add_object(&array, "", &o2);
+       json_add_object(&array, "will-be-ignored", &o1);
+       json_add_object(&array, NULL, NULL);
+
+       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));
+
+       json_free(&array);
+
+}
+
+static void test_json_add_timestamp(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;
+
+
+       object = json_new_object();
+       before = time(NULL);
+       json_add_timestamp(&object);
+       after = time(NULL);
+
+       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);
+
+       json_free(&object);
+}
+
+static void test_json_add_stringn(void **state)
+{
+       struct json_object object;
+       struct json_t *value = NULL;
+       const char *s = NULL;
+
+       object = json_new_object();
+       json_add_stringn(&object, "null", NULL, 10);
+       json_add_stringn(&object, "null-zero-len", NULL, 0);
+       json_add_stringn(&object, "empty", "", 1);
+       json_add_stringn(&object, "empty-zero-len", "", 0);
+       json_add_stringn(&object, "value-less-than-len", "123456", 7);
+       json_add_stringn(&object, "value-greater-than-len", "abcd", 3);
+       json_add_stringn(&object, "value-equal-len", "ZYX", 3);
+       json_add_stringn(&object, "value-len-is-zero", "this will be null", 0);
+
+
+       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));
+
+       json_free(&object);
+}
+
+static void test_json_add_version(void **state)
+{
+       struct json_object object;
+       struct json_t *version = NULL;
+       struct json_t *v = NULL;
+       double n;
+
+       object = json_new_object();
+       json_add_version(&object, 3, 1);
+
+       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);
+
+       json_free(&object);
+}
+
+static void test_json_add_address(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;
+       const char *s = NULL;
+       int rc;
+
+       TALLOC_CTX *ctx = talloc_new(NULL);
+
+       object = json_new_object();
+
+       json_add_address(&object, "null", NULL);
+
+       rc = tsocket_address_inet_from_strings(
+               ctx,
+               "ip",
+               "127.0.0.1",
+               21,
+               &ip4);
+       assert_int_equal(0, rc);
+       json_add_address(&object, "ip4", ip4);
+
+       rc = tsocket_address_inet_from_strings(
+               ctx,
+               "ip",
+               "2001:db8:0:0:1:0:0:1",
+               42,
+               &ip6);
+       assert_int_equal(0, rc);
+       json_add_address(&object, "ip6", ip6);
+
+       rc = tsocket_address_unix_from_path(ctx, "/samba/pipe", &pipe);
+       assert_int_equal(0, rc);
+       json_add_address(&object, "pipe", pipe);
+
+       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);
+
+       json_free(&object);
+       TALLOC_FREE(ctx);
+}
+
+static void test_json_add_sid(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;
+
+
+       object = json_new_object();
+
+       json_add_sid(&object, "null", NULL);
+
+       assert_true(string_to_sid(&sid, SID));
+       json_add_sid(&object, "sid", &sid);
+
+       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);
+       json_free(&object);
+}
+
+static void test_json_add_guid(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;
+
+
+       object = json_new_object();
+
+       json_add_guid(&object, "null", NULL);
+
+       status = GUID_from_string(GUID, &guid);
+       assert_true(NT_STATUS_IS_OK(status));
+       json_add_guid(&object, "guid", &guid);
+
+       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);
+
+       json_free(&object);
+}
+
+static void test_json_to_string(void **state)
+{
+       struct json_object object;
+       char *s = NULL;
+
+       TALLOC_CTX *ctx = talloc_new(NULL);
+
+       object = json_new_object();
+       object.error = true;
+
+       s = json_to_string(ctx, &object);
+       assert_null(s);
+
+       object.error = false;
+       s = json_to_string(ctx, &object);
+       assert_string_equal("{}", s);
+       TALLOC_FREE(s);
+
+       json_add_string(&object, "name", "value");
+       s = json_to_string(ctx, &object);
+       assert_string_equal("{\"name\": \"value\"}", s);
+       TALLOC_FREE(s);
+
+       json_free(&object);
+       TALLOC_FREE(ctx);
+}
+#endif
+
+static void test_audit_get_timestamp(void **state)
+{
+       const char *t = NULL;
+       char *c;
+       struct tm tm;
+       time_t before;
+       time_t after;
+       time_t actual;
+
+       TALLOC_CTX *ctx = talloc_new(NULL);
+
+       before = time(NULL);
+       t = audit_get_timestamp(ctx);
+       after = time(NULL);
+
+
+       c = strptime(t, "%a, %d %b %Y %H:%M:%S", &tm);
+       tm.tm_isdst = -1;
+       if (c != NULL && *c == '.') {
+               char *e;
+               strtod(c, &e);
+               c = e;
+       }
+       if (c != NULL && *c == ' ') {
+               struct tm tz;
+               c = strptime(c, " %Z", &tz);
+       }
+       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(int argc, const char **argv)
+{
+       const struct CMUnitTest tests[] = {
+#ifdef HAVE_JANSSON
+               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),
+#endif
+               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 (file)
index 0000000..4022d90
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+
+bld.SAMBA_SUBSYSTEM(
+    'audit_logging',
+    deps='''MESSAGING_SEND
+            jansson
+            samba-debug
+            LIBTSOCKET''',
+    source='audit_logging.c'
+)
+
+bld.SAMBA_BINARY(
+    'audit_logging_test',
+    source='tests/audit_logging_test.c',
+    deps='''
+        audit_logging
+        jansson
+        cmocka
+        talloc
+        samba-util
+        LIBTSOCKET
+    ''',
+    install=False
+)
index 9740118..b1d91ef 100755 (executable)
@@ -1065,3 +1065,5 @@ plantestsuite("samba4.dsdb.samdb.ldb_modules.unique_object_sids" , "none",
               [os.path.join(bindir(), "test_unique_object_sids")])
 plantestsuite("samba4.dsdb.samdb.ldb_modules.encrypted_secrets", "none",
                   [os.path.join(bindir(), "test_encrypted_secrets")])
+plantestsuite("lib.audit_logging.audit_logging", "none",
+                  [os.path.join(bindir(), "audit_logging_test")])
index 8a59bdf..5454876 100644 (file)
@@ -46,6 +46,7 @@ bld.RECURSE('lib/texpect')
 bld.RECURSE('lib/addns')
 bld.RECURSE('lib/ldb')
 bld.RECURSE('lib/param')
+bld.RECURSE('lib/audit_logging')
 bld.RECURSE('dynconfig')
 bld.RECURSE('lib/util/charset')
 bld.RECURSE('python')