diff options
Diffstat (limited to 'source4/dsdb/samdb/ldb_modules/audit_log.c')
-rw-r--r-- | source4/dsdb/samdb/ldb_modules/audit_log.c | 1913 |
1 files changed, 1913 insertions, 0 deletions
diff --git a/source4/dsdb/samdb/ldb_modules/audit_log.c b/source4/dsdb/samdb/ldb_modules/audit_log.c new file mode 100644 index 0000000..7cc3ff6 --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/audit_log.c @@ -0,0 +1,1913 @@ +/* + ldb database 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/>. +*/ + +/* + * Provide an audit log of changes made to the database and at a + * higher level details of any password changes and resets. + * + */ + +#include "includes.h" +#include "ldb_module.h" +#include "lib/audit_logging/audit_logging.h" + +#include "dsdb/samdb/samdb.h" +#include "dsdb/samdb/ldb_modules/util.h" +#include "dsdb/samdb/ldb_modules/audit_util_proto.h" +#include "libcli/security/dom_sid.h" +#include "auth/common_auth.h" +#include "param/param.h" +#include "librpc/gen_ndr/windows_event_ids.h" + +#define OPERATION_JSON_TYPE "dsdbChange" +#define OPERATION_HR_TAG "DSDB Change" +#define OPERATION_MAJOR 1 +#define OPERATION_MINOR 0 +#define OPERATION_LOG_LVL 5 + +#define PASSWORD_JSON_TYPE "passwordChange" +#define PASSWORD_HR_TAG "Password Change" +#define PASSWORD_MAJOR 1 +#define PASSWORD_MINOR 1 +#define PASSWORD_LOG_LVL 5 + +#define TRANSACTION_JSON_TYPE "dsdbTransaction" +#define TRANSACTION_HR_TAG "DSDB Transaction" +#define TRANSACTION_MAJOR 1 +#define TRANSACTION_MINOR 0 +#define TRANSACTION_LOG_FAILURE_LVL 5 +#define TRANSACTION_LOG_COMPLETION_LVL 10 + +#define REPLICATION_JSON_TYPE "replicatedUpdate" +#define REPLICATION_HR_TAG "Replicated Update" +#define REPLICATION_MAJOR 1 +#define REPLICATION_MINOR 0 +#define REPLICATION_LOG_LVL 5 +/* + * Attribute values are truncated in the logs if they are longer than + * MAX_LENGTH + */ +#define MAX_LENGTH 1024 + +#define min(a, b) (((a)>(b))?(b):(a)) + +/* + * Private data for the module, stored in the ldb_module private data + */ +struct audit_private { + /* + * Should details of database operations be sent over the + * messaging bus. + */ + bool send_samdb_events; + /* + * Should details of password changes and resets be sent over + * the messaging bus. + */ + bool send_password_events; + /* + * The messaging context to send the messages over. Will only + * be set if send_samdb_events or send_password_events are + * true. + */ + struct imessaging_context *msg_ctx; + /* + * Unique transaction id for the current transaction + */ + struct GUID transaction_guid; + /* + * Transaction start time, used to calculate the transaction + * duration. + */ + struct timeval transaction_start; +}; + +/* + * @brief Has the password changed. + * + * Does the message contain a change to one of the password attributes? The + * password attributes are defined in DSDB_PASSWORD_ATTRIBUTES + * + * @return true if the message contains a password attribute + * + */ +static bool has_password_changed(const struct ldb_message *message) +{ + unsigned int i; + if (message == NULL) { + return false; + } + for (i=0;i<message->num_elements;i++) { + if (dsdb_audit_is_password_attribute( + message->elements[i].name)) { + return true; + } + } + return false; +} + +/* + * @brief get the password change windows event id + * + * Get the Windows Event Id for the action being performed on the user password. + * + * This routine assumes that the request contains password attributes and that the + * password ACL checks have been performed by acl.c + * + * @param request the ldb_request to inspect + * @param reply the ldb_reply, will contain the password controls + * + * @return The windows event code. + */ +static enum event_id_type get_password_windows_event_id( + const struct ldb_request *request, + const struct ldb_reply *reply) +{ + if(request->operation == LDB_ADD) { + return EVT_ID_PASSWORD_RESET; + } else { + struct ldb_control *pav_ctrl = NULL; + struct dsdb_control_password_acl_validation *pav = NULL; + + pav_ctrl = ldb_reply_get_control( + discard_const(reply), + DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID); + if (pav_ctrl == NULL) { + return EVT_ID_PASSWORD_RESET; + } + + pav = talloc_get_type_abort( + pav_ctrl->data, + struct dsdb_control_password_acl_validation); + + if (pav->pwd_reset) { + return EVT_ID_PASSWORD_RESET; + } else { + return EVT_ID_PASSWORD_CHANGE; + } + } +} +/* + * @brief Is the request a password "Change" or a "Reset" + * + * Get a description of the action being performed on the user password. This + * routine assumes that the request contains password attributes and that the + * password ACL checks have been performed by acl.c + * + * @param request the ldb_request to inspect + * @param reply the ldb_reply, will contain the password controls + * + * @return "Change" if the password is being changed. + * "Reset" if the password is being reset. + */ +static const char *get_password_action( + const struct ldb_request *request, + const struct ldb_reply *reply) +{ + if(request->operation == LDB_ADD) { + return "Reset"; + } else { + struct ldb_control *pav_ctrl = NULL; + struct dsdb_control_password_acl_validation *pav = NULL; + + pav_ctrl = ldb_reply_get_control( + discard_const(reply), + DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID); + if (pav_ctrl == NULL) { + return "Reset"; + } + + pav = talloc_get_type_abort( + pav_ctrl->data, + struct dsdb_control_password_acl_validation); + + if (pav->pwd_reset) { + return "Reset"; + } else { + return "Change"; + } + } +} + +/* + * @brief generate a JSON object detailing an ldb operation. + * + * Generate a JSON object detailing an ldb operation. + * + * @param module the ldb module + * @param request the request + * @param reply the result of the operation. + * + * @return the generated JSON object, should be freed with json_free. + * + * + */ +static struct json_object operation_json( + struct ldb_module *module, + const struct ldb_request *request, + const struct ldb_reply *reply) +{ + struct ldb_context *ldb = NULL; + const struct dom_sid *sid = NULL; + bool as_system = false; + struct json_object wrapper = json_empty_object; + struct json_object audit = json_empty_object; + const struct tsocket_address *remote = NULL; + const char *dn = NULL; + const char* operation = NULL; + const struct GUID *unique_session_token = NULL; + const struct ldb_message *message = NULL; + struct audit_private *audit_private + = talloc_get_type_abort(ldb_module_get_private(module), + struct audit_private); + int rc = 0; + + ldb = ldb_module_get_ctx(module); + + remote = dsdb_audit_get_remote_address(ldb); + if (remote != NULL && dsdb_audit_is_system_session(module)) { + as_system = true; + sid = dsdb_audit_get_actual_sid(ldb); + unique_session_token = + dsdb_audit_get_actual_unique_session_token(ldb); + } else { + sid = dsdb_audit_get_user_sid(module); + unique_session_token = + dsdb_audit_get_unique_session_token(module); + } + dn = dsdb_audit_get_primary_dn(request); + operation = dsdb_audit_get_operation_name(request); + + audit = json_new_object(); + if (json_is_invalid(&audit)) { + goto failure; + } + rc = json_add_version(&audit, OPERATION_MAJOR, OPERATION_MINOR); + if (rc != 0) { + goto failure; + } + rc = json_add_int(&audit, "statusCode", reply->error); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&audit, "status", ldb_strerror(reply->error)); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&audit, "operation", operation); + if (rc != 0) { + goto failure; + } + rc = json_add_address(&audit, "remoteAddress", remote); + if (rc != 0) { + goto failure; + } + rc = json_add_bool(&audit, "performedAsSystem", as_system); + if (rc != 0) { + goto failure; + } + rc = json_add_sid(&audit, "userSid", sid); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&audit, "dn", dn); + if (rc != 0) { + goto failure; + } + rc = json_add_guid( + &audit, "transactionId", &audit_private->transaction_guid); + if (rc != 0) { + goto failure; + } + rc = json_add_guid(&audit, "sessionId", unique_session_token); + if (rc != 0) { + goto failure; + } + + message = dsdb_audit_get_message(request); + if (message != NULL) { + struct json_object attributes = + dsdb_audit_attributes_json( + request->operation, + message); + if (json_is_invalid(&attributes)) { + goto failure; + } + rc = json_add_object(&audit, "attributes", &attributes); + if (rc != 0) { + goto failure; + } + } + + wrapper = json_new_object(); + if (json_is_invalid(&wrapper)) { + goto failure; + } + rc = json_add_timestamp(&wrapper); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&wrapper, "type", OPERATION_JSON_TYPE); + if (rc != 0) { + goto failure; + } + rc = json_add_object(&wrapper, OPERATION_JSON_TYPE, &audit); + if (rc != 0) { + goto failure; + } + return wrapper; + +failure: + /* + * On a failure audit will not have been added to wrapper so it + * needs to free it to avoid a leak. + * + * wrapper is freed to invalidate it as it will have only been + * partially constructed and may be inconsistent. + * + * All the json manipulation routines handle a freed object correctly + */ + json_free(&audit); + json_free(&wrapper); + DBG_ERR("Unable to create ldb operation JSON audit message\n"); + return wrapper; +} + +/* + * @brief generate a JSON object detailing a replicated update. + * + * Generate a JSON object detailing a replicated update + * + * @param module the ldb module + * @param request the request + * @paran reply the result of the operation + * + * @return the generated JSON object, should be freed with json_free. + * NULL if there was an error generating the message. + * + */ +static struct json_object replicated_update_json( + struct ldb_module *module, + const struct ldb_request *request, + const struct ldb_reply *reply) +{ + struct json_object wrapper = json_empty_object; + struct json_object audit = json_empty_object; + struct audit_private *audit_private + = talloc_get_type_abort(ldb_module_get_private(module), + struct audit_private); + struct dsdb_extended_replicated_objects *ro = talloc_get_type( + request->op.extended.data, + struct dsdb_extended_replicated_objects); + const char *partition_dn = NULL; + const char *error = NULL; + int rc = 0; + + partition_dn = ldb_dn_get_linearized(ro->partition_dn); + error = get_friendly_werror_msg(ro->error); + + audit = json_new_object(); + if (json_is_invalid(&audit)) { + goto failure; + } + rc = json_add_version(&audit, REPLICATION_MAJOR, REPLICATION_MINOR); + if (rc != 0) { + goto failure; + } + rc = json_add_int(&audit, "statusCode", reply->error); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&audit, "status", ldb_strerror(reply->error)); + if (rc != 0) { + goto failure; + } + rc = json_add_guid( + &audit, "transactionId", &audit_private->transaction_guid); + if (rc != 0) { + goto failure; + } + rc = json_add_int(&audit, "objectCount", ro->num_objects); + if (rc != 0) { + goto failure; + } + rc = json_add_int(&audit, "linkCount", ro->linked_attributes_count); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&audit, "partitionDN", partition_dn); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&audit, "error", error); + if (rc != 0) { + goto failure; + } + rc = json_add_int(&audit, "errorCode", W_ERROR_V(ro->error)); + if (rc != 0) { + goto failure; + } + rc = json_add_guid( + &audit, "sourceDsa", &ro->source_dsa->source_dsa_obj_guid); + if (rc != 0) { + goto failure; + } + rc = json_add_guid( + &audit, "invocationId", &ro->source_dsa->source_dsa_invocation_id); + if (rc != 0) { + goto failure; + } + + wrapper = json_new_object(); + if (json_is_invalid(&wrapper)) { + goto failure; + } + rc = json_add_timestamp(&wrapper); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&wrapper, "type", REPLICATION_JSON_TYPE); + if (rc != 0) { + goto failure; + } + rc = json_add_object(&wrapper, REPLICATION_JSON_TYPE, &audit); + if (rc != 0) { + goto failure; + } + return wrapper; +failure: + /* + * On a failure audit will not have been added to wrapper so it + * needs to be freed it to avoid a leak. + * + * wrapper is freed to invalidate it as it will have only been + * partially constructed and may be inconsistent. + * + * All the json manipulation routines handle a freed object correctly + */ + json_free(&audit); + json_free(&wrapper); + DBG_ERR("Unable to create replicated update JSON audit message\n"); + return wrapper; +} + +/* + * @brief generate a JSON object detailing a password change. + * + * Generate a JSON object detailing a password change. + * + * @param module the ldb module + * @param request the request + * @param reply the result/response + * @param status the status code returned for the underlying ldb operation. + * + * @return the generated JSON object. + * + */ +static struct json_object password_change_json( + struct ldb_module *module, + const struct ldb_request *request, + const struct ldb_reply *reply) +{ + struct ldb_context *ldb = NULL; + const struct dom_sid *sid = NULL; + const char *dn = NULL; + struct json_object wrapper = json_empty_object; + struct json_object audit = json_empty_object; + const struct tsocket_address *remote = NULL; + const char* action = NULL; + const struct GUID *unique_session_token = NULL; + struct audit_private *audit_private + = talloc_get_type_abort(ldb_module_get_private(module), + struct audit_private); + int rc = 0; + enum event_id_type event_id; + + ldb = ldb_module_get_ctx(module); + + remote = dsdb_audit_get_remote_address(ldb); + sid = dsdb_audit_get_user_sid(module); + dn = dsdb_audit_get_primary_dn(request); + action = get_password_action(request, reply); + unique_session_token = dsdb_audit_get_unique_session_token(module); + event_id = get_password_windows_event_id(request, reply); + + audit = json_new_object(); + if (json_is_invalid(&audit)) { + goto failure; + } + rc = json_add_version(&audit, PASSWORD_MAJOR, PASSWORD_MINOR); + if (rc != 0) { + goto failure; + } + rc = json_add_int(&audit, "eventId", event_id); + if (rc != 0) { + goto failure; + } + rc = json_add_int(&audit, "statusCode", reply->error); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&audit, "status", ldb_strerror(reply->error)); + if (rc != 0) { + goto failure; + } + rc = json_add_address(&audit, "remoteAddress", remote); + if (rc != 0) { + goto failure; + } + rc = json_add_sid(&audit, "userSid", sid); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&audit, "dn", dn); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&audit, "action", action); + if (rc != 0) { + goto failure; + } + rc = json_add_guid( + &audit, "transactionId", &audit_private->transaction_guid); + if (rc != 0) { + goto failure; + } + rc = json_add_guid(&audit, "sessionId", unique_session_token); + if (rc != 0) { + goto failure; + } + + wrapper = json_new_object(); + if (json_is_invalid(&wrapper)) { + goto failure; + } + rc = json_add_timestamp(&wrapper); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&wrapper, "type", PASSWORD_JSON_TYPE); + if (rc != 0) { + goto failure; + } + rc = json_add_object(&wrapper, PASSWORD_JSON_TYPE, &audit); + if (rc != 0) { + goto failure; + } + + return wrapper; +failure: + /* + * On a failure audit will not have been added to wrapper so it + * needs to free it to avoid a leak. + * + * wrapper is freed to invalidate it as it will have only been + * partially constructed and may be inconsistent. + * + * All the json manipulation routines handle a freed object correctly + */ + json_free(&wrapper); + json_free(&audit); + DBG_ERR("Unable to create password change JSON audit message\n"); + return wrapper; +} + + +/* + * @brief create a JSON object containing details of a transaction event. + * + * Create a JSON object detailing a transaction transaction life cycle events, + * i.e. begin, commit, roll back + * + * @param action a one word description of the event/action + * @param transaction_id the GUID identifying the current transaction. + * @param status the status code returned by the operation + * @param duration the duration of the operation. + * + * @return a JSON object detailing the event + */ +static struct json_object transaction_json( + const char *action, + struct GUID *transaction_id, + const int64_t duration) +{ + struct json_object wrapper = json_empty_object; + struct json_object audit = json_empty_object; + int rc = 0; + + audit = json_new_object(); + if (json_is_invalid(&audit)) { + goto failure; + } + + rc = json_add_version(&audit, TRANSACTION_MAJOR, TRANSACTION_MINOR); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&audit, "action", action); + if (rc != 0) { + goto failure; + } + rc = json_add_guid(&audit, "transactionId", transaction_id); + if (rc != 0) { + goto failure; + } + rc = json_add_int(&audit, "duration", duration); + if (rc != 0) { + goto failure; + } + + wrapper = json_new_object(); + if (json_is_invalid(&wrapper)) { + goto failure; + } + rc = json_add_timestamp(&wrapper); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&wrapper, "type", TRANSACTION_JSON_TYPE); + if (rc != 0) { + goto failure; + } + rc = json_add_object(&wrapper, TRANSACTION_JSON_TYPE, &audit); + if (rc != 0) { + goto failure; + } + + return wrapper; +failure: + /* + * On a failure audit will not have been added to wrapper so it + * needs to free it to avoid a leak. + * + * wrapper is freed to invalidate it as it will have only been + * partially constructed and may be inconsistent. + * + * All the json manipulation routines handle a freed object correctly + */ + json_free(&wrapper); + json_free(&audit); + DBG_ERR("Unable to create transaction JSON audit message\n"); + return wrapper; +} + + +/* + * @brief generate a JSON object detailing a commit failure. + * + * Generate a JSON object containing details of a commit failure. + * + * @param action the commit action, "commit" or "prepare" + * @param status the status code returned by commit + * @param reason any extra failure information/reason available + * @param transaction_id the GUID identifying the current transaction. + */ +static struct json_object commit_failure_json( + const char *action, + const int64_t duration, + int status, + const char *reason, + struct GUID *transaction_id) +{ + struct json_object wrapper = json_empty_object; + struct json_object audit = json_empty_object; + int rc = 0; + + audit = json_new_object(); + if (json_is_invalid(&audit)) { + goto failure; + } + rc = json_add_version(&audit, TRANSACTION_MAJOR, TRANSACTION_MINOR); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&audit, "action", action); + if (rc != 0) { + goto failure; + } + rc = json_add_guid(&audit, "transactionId", transaction_id); + if (rc != 0) { + goto failure; + } + rc = json_add_int(&audit, "duration", duration); + if (rc != 0) { + goto failure; + } + rc = json_add_int(&audit, "statusCode", status); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&audit, "status", ldb_strerror(status)); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&audit, "reason", reason); + if (rc != 0) { + goto failure; + } + + wrapper = json_new_object(); + if (json_is_invalid(&wrapper)) { + goto failure; + } + rc = json_add_timestamp(&wrapper); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&wrapper, "type", TRANSACTION_JSON_TYPE); + if (rc != 0) { + goto failure; + } + rc = json_add_object(&wrapper, TRANSACTION_JSON_TYPE, &audit); + if (rc != 0) { + goto failure; + } + + return wrapper; +failure: + /* + * On a failure audit will not have been added to wrapper so it + * needs to free it to avoid a leak. + * + * wrapper is freed to invalidate it as it will have only been + * partially constructed and may be inconsistent. + * + * All the json manipulation routines handle a freed object correctly + */ + json_free(&audit); + json_free(&wrapper); + DBG_ERR("Unable to create commit failure JSON audit message\n"); + return wrapper; +} + +/* + * @brief Print a human readable log line for a password change event. + * + * Generate a human readable log line detailing a password change. + * + * @param mem_ctx The talloc context that will own the generated log line. + * @param module the ldb module + * @param request the request + * @param reply the result/response + * @param status the status code returned for the underlying ldb operation. + * + * @return the generated log line. + */ +static char *password_change_human_readable( + TALLOC_CTX *mem_ctx, + struct ldb_module *module, + const struct ldb_request *request, + const struct ldb_reply *reply) +{ + struct ldb_context *ldb = NULL; + const char *remote_host = NULL; + const struct dom_sid *sid = NULL; + struct dom_sid_buf user_sid; + const char *timestamp = NULL; + char *log_entry = NULL; + const char *action = NULL; + const char *dn = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_module_get_ctx(module); + + remote_host = dsdb_audit_get_remote_host(ldb, ctx); + sid = dsdb_audit_get_user_sid(module); + timestamp = audit_get_timestamp(ctx); + action = get_password_action(request, reply); + dn = dsdb_audit_get_primary_dn(request); + + log_entry = talloc_asprintf( + mem_ctx, + "[%s] at [%s] status [%s] " + "remote host [%s] SID [%s] DN [%s]", + action, + timestamp, + ldb_strerror(reply->error), + remote_host, + dom_sid_str_buf(sid, &user_sid), + dn); + TALLOC_FREE(ctx); + return log_entry; +} +/* + * @brief Generate a human readable string, detailing attributes in a message + * + * For modify operations each attribute is prefixed with the action. + * Normal values are enclosed in [] + * Base64 values are enclosed in {} + * Truncated values are indicated by three trailing dots "..." + * + * @param ldb The ldb_context + * @param buffer The attributes will be appended to the buffer. + * assumed to have been allocated via talloc. + * @param operation The operation type + * @param message the message to process + * + */ +static char *log_attributes( + struct ldb_context *ldb, + char *buffer, + enum ldb_request_type operation, + const struct ldb_message *message) +{ + size_t i, j; + for (i=0;i<message->num_elements;i++) { + if (i > 0) { + buffer = talloc_asprintf_append_buffer(buffer, " "); + } + + if (message->elements[i].name == NULL) { + ldb_debug( + ldb, + LDB_DEBUG_ERROR, + "Error: Invalid element name (NULL) at " + "position %zu", i); + return NULL; + } + + if (operation == LDB_MODIFY) { + const char *action =NULL; + action = dsdb_audit_get_modification_action( + message->elements[i].flags); + buffer = talloc_asprintf_append_buffer( + buffer, + "%s: %s ", + action, + message->elements[i].name); + } else { + buffer = talloc_asprintf_append_buffer( + buffer, + "%s ", + message->elements[i].name); + } + + if (dsdb_audit_redact_attribute(message->elements[i].name)) { + /* + * Do not log the value of any secret or password + * attributes + */ + buffer = talloc_asprintf_append_buffer( + buffer, + "[REDACTED SECRET ATTRIBUTE]"); + continue; + } + + for (j=0;j<message->elements[i].num_values;j++) { + struct ldb_val v; + bool use_b64_encode = false; + size_t length; + if (j > 0) { + buffer = talloc_asprintf_append_buffer( + buffer, + " "); + } + + v = message->elements[i].values[j]; + length = min(MAX_LENGTH, v.length); + use_b64_encode = ldb_should_b64_encode(ldb, &v); + if (use_b64_encode) { + const char *encoded = ldb_base64_encode( + buffer, + (char *)v.data, + length); + buffer = talloc_asprintf_append_buffer( + buffer, + "{%s%s}", + encoded, + (v.length > MAX_LENGTH ? "..." : "")); + } else { + buffer = talloc_asprintf_append_buffer( + buffer, + "[%*.*s%s]", + (int)length, + (int)length, + (char *)v.data, + (v.length > MAX_LENGTH ? "..." : "")); + } + } + } + return buffer; +} + +/* + * @brief generate a human readable log entry detailing an ldb operation. + * + * Generate a human readable log entry detailing an ldb operation. + * + * @param mem_ctx The talloc context owning the returned string. + * @param module the ldb module + * @param request the request + * @param reply the result of the operation + * + * @return the log entry. + * + */ +static char *operation_human_readable( + TALLOC_CTX *mem_ctx, + struct ldb_module *module, + const struct ldb_request *request, + const struct ldb_reply *reply) +{ + struct ldb_context *ldb = NULL; + const char *remote_host = NULL; + const struct tsocket_address *remote = NULL; + const struct dom_sid *sid = NULL; + struct dom_sid_buf user_sid; + const char *timestamp = NULL; + const char *op_name = NULL; + char *log_entry = NULL; + const char *dn = NULL; + const char *new_dn = NULL; + const struct ldb_message *message = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + ldb = ldb_module_get_ctx(module); + + remote_host = dsdb_audit_get_remote_host(ldb, ctx); + remote = dsdb_audit_get_remote_address(ldb); + if (remote != NULL && dsdb_audit_is_system_session(module)) { + sid = dsdb_audit_get_actual_sid(ldb); + } else { + sid = dsdb_audit_get_user_sid(module); + } + timestamp = audit_get_timestamp(ctx); + op_name = dsdb_audit_get_operation_name(request); + dn = dsdb_audit_get_primary_dn(request); + new_dn = dsdb_audit_get_secondary_dn(request); + + message = dsdb_audit_get_message(request); + + log_entry = talloc_asprintf( + mem_ctx, + "[%s] at [%s] status [%s] " + "remote host [%s] SID [%s] DN [%s]", + op_name, + timestamp, + ldb_strerror(reply->error), + remote_host, + dom_sid_str_buf(sid, &user_sid), + dn); + if (new_dn != NULL) { + log_entry = talloc_asprintf_append_buffer( + log_entry, + " New DN [%s]", + new_dn); + } + if (message != NULL) { + log_entry = talloc_asprintf_append_buffer(log_entry, + " attributes ["); + log_entry = log_attributes(ldb, + log_entry, + request->operation, + message); + log_entry = talloc_asprintf_append_buffer(log_entry, "]"); + } + TALLOC_FREE(ctx); + return log_entry; +} + +/* + * @brief generate a human readable log entry detailing a replicated update + * operation. + * + * Generate a human readable log entry detailing a replicated update operation + * + * @param mem_ctx The talloc context owning the returned string. + * @param module the ldb module + * @param request the request + * @param reply the result of the operation. + * + * @return the log entry. + * + */ +static char *replicated_update_human_readable( + TALLOC_CTX *mem_ctx, + struct ldb_module *module, + const struct ldb_request *request, + const struct ldb_reply *reply) +{ + struct dsdb_extended_replicated_objects *ro = talloc_get_type( + request->op.extended.data, + struct dsdb_extended_replicated_objects); + const char *partition_dn = NULL; + const char *error = NULL; + char *log_entry = NULL; + char *timestamp = NULL; + struct GUID_txt_buf object_buf; + const char *object = NULL; + struct GUID_txt_buf invocation_buf; + const char *invocation = NULL; + + + TALLOC_CTX *ctx = talloc_new(NULL); + + timestamp = audit_get_timestamp(ctx); + error = get_friendly_werror_msg(ro->error); + partition_dn = ldb_dn_get_linearized(ro->partition_dn); + object = GUID_buf_string( + &ro->source_dsa->source_dsa_obj_guid, + &object_buf); + invocation = GUID_buf_string( + &ro->source_dsa->source_dsa_invocation_id, + &invocation_buf); + + + log_entry = talloc_asprintf( + mem_ctx, + "at [%s] status [%s] error [%s] partition [%s] objects [%d] " + "links [%d] object [%s] invocation [%s]", + timestamp, + ldb_strerror(reply->error), + error, + partition_dn, + ro->num_objects, + ro->linked_attributes_count, + object, + invocation); + + TALLOC_FREE(ctx); + return log_entry; +} +/* + * @brief create a human readable log entry detailing a transaction event. + * + * Create a human readable log entry detailing a transaction event. + * i.e. begin, commit, roll back + * + * @param mem_ctx The talloc context owning the returned string. + * @param action a one word description of the event/action + * @param duration the duration of the transaction. + * + * @return the log entry + */ +static char *transaction_human_readable( + TALLOC_CTX *mem_ctx, + const char* action, + const int64_t duration) +{ + const char *timestamp = NULL; + char *log_entry = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + timestamp = audit_get_timestamp(ctx); + + log_entry = talloc_asprintf( + mem_ctx, + "[%s] at [%s] duration [%"PRIi64"]", + action, + timestamp, + duration); + + TALLOC_FREE(ctx); + return log_entry; +} + +/* + * @brief generate a human readable log entry detailing a commit failure. + * + * Generate generate a human readable log entry detailing a commit failure. + * + * @param mem_ctx The talloc context owning the returned string. + * @param action the commit action, "prepare" or "commit" + * @param status the status code returned by commit + * @param reason any extra failure information/reason available + * + * @return the log entry + */ +static char *commit_failure_human_readable( + TALLOC_CTX *mem_ctx, + const char *action, + const int64_t duration, + int status, + const char *reason) +{ + const char *timestamp = NULL; + char *log_entry = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + timestamp = audit_get_timestamp(ctx); + + log_entry = talloc_asprintf( + mem_ctx, + "[%s] at [%s] duration [%"PRIi64"] status [%d] reason [%s]", + action, + timestamp, + duration, + status, + reason); + + TALLOC_FREE(ctx); + return log_entry; +} + +/* + * @brief log details of a standard ldb operation. + * + * Log the details of an ldb operation in JSON and or human readable format + * and send over the message bus. + * + * @param module the ldb_module + * @param request the operation request. + * @param reply the operation result. + * @param the status code returned for the operation. + * + */ +static void log_standard_operation( + struct ldb_module *module, + const struct ldb_request *request, + const struct ldb_reply *reply) +{ + + const struct ldb_message *message = dsdb_audit_get_message(request); + bool password_changed = has_password_changed(message); + struct audit_private *audit_private = + talloc_get_type_abort(ldb_module_get_private(module), + struct audit_private); + + TALLOC_CTX *ctx = talloc_new(NULL); + + if (CHECK_DEBUGLVLC(DBGC_DSDB_AUDIT, OPERATION_LOG_LVL)) { + char *entry = NULL; + entry = operation_human_readable( + ctx, + module, + request, + reply); + audit_log_human_text( + OPERATION_HR_TAG, + entry, + DBGC_DSDB_AUDIT, + OPERATION_LOG_LVL); + TALLOC_FREE(entry); + } + if (CHECK_DEBUGLVLC(DBGC_DSDB_PWD_AUDIT, PASSWORD_LOG_LVL)) { + if (password_changed) { + char *entry = NULL; + entry = password_change_human_readable( + ctx, + module, + request, + reply); + audit_log_human_text( + PASSWORD_HR_TAG, + entry, + DBGC_DSDB_PWD_AUDIT, + PASSWORD_LOG_LVL); + TALLOC_FREE(entry); + } + } + if (CHECK_DEBUGLVLC(DBGC_DSDB_AUDIT_JSON, OPERATION_LOG_LVL) || + (audit_private->msg_ctx + && audit_private->send_samdb_events)) { + struct json_object json; + json = operation_json(module, request, reply); + audit_log_json( + &json, + DBGC_DSDB_AUDIT_JSON, + OPERATION_LOG_LVL); + if (audit_private->msg_ctx + && audit_private->send_samdb_events) { + audit_message_send( + audit_private->msg_ctx, + DSDB_EVENT_NAME, + MSG_DSDB_LOG, + &json); + } + json_free(&json); + } + if (CHECK_DEBUGLVLC(DBGC_DSDB_PWD_AUDIT_JSON, PASSWORD_LOG_LVL) || + (audit_private->msg_ctx + && audit_private->send_password_events)) { + if (password_changed) { + struct json_object json; + json = password_change_json(module, request, reply); + audit_log_json( + &json, + DBGC_DSDB_PWD_AUDIT_JSON, + PASSWORD_LOG_LVL); + if (audit_private->send_password_events) { + audit_message_send( + audit_private->msg_ctx, + DSDB_PWD_EVENT_NAME, + MSG_DSDB_PWD_LOG, + &json); + } + json_free(&json); + } + } + TALLOC_FREE(ctx); +} + +/* + * @brief log details of a replicated update. + * + * Log the details of a replicated update in JSON and or human readable + * format and send over the message bus. + * + * @param module the ldb_module + * @param request the operation request + * @param reply the result of the operation. + * + */ +static void log_replicated_operation( + struct ldb_module *module, + const struct ldb_request *request, + const struct ldb_reply *reply) +{ + + struct audit_private *audit_private = + talloc_get_type_abort(ldb_module_get_private(module), + struct audit_private); + + TALLOC_CTX *ctx = talloc_new(NULL); + + if (CHECK_DEBUGLVLC(DBGC_DSDB_AUDIT, REPLICATION_LOG_LVL)) { + char *entry = NULL; + entry = replicated_update_human_readable( + ctx, + module, + request, + reply); + audit_log_human_text( + REPLICATION_HR_TAG, + entry, + DBGC_DSDB_AUDIT, + REPLICATION_LOG_LVL); + TALLOC_FREE(entry); + } + if (CHECK_DEBUGLVLC(DBGC_DSDB_AUDIT_JSON, REPLICATION_LOG_LVL) || + (audit_private->msg_ctx && audit_private->send_samdb_events)) { + struct json_object json; + json = replicated_update_json(module, request, reply); + audit_log_json( + &json, + DBGC_DSDB_AUDIT_JSON, + REPLICATION_LOG_LVL); + if (audit_private->send_samdb_events) { + audit_message_send( + audit_private->msg_ctx, + DSDB_EVENT_NAME, + MSG_DSDB_LOG, + &json); + } + json_free(&json); + } + TALLOC_FREE(ctx); +} + +/* + * @brief log details of an ldb operation. + * + * Log the details of an ldb operation in JSON and or human readable format + * and send over the message bus. + * + * @param module the ldb_module + * @param request the operation request + * @part reply the result of the operation + * + */ +static void log_operation( + struct ldb_module *module, + const struct ldb_request *request, + const struct ldb_reply *reply) +{ + + if (request->operation == LDB_EXTENDED) { + if (strcmp( + request->op.extended.oid, + DSDB_EXTENDED_REPLICATED_OBJECTS_OID) != 0) { + + log_replicated_operation(module, request, reply); + } + } else { + log_standard_operation(module, request, reply); + } +} + +/* + * @brief log details of a transaction event. + * + * Log the details of a transaction event in JSON and or human readable format + * and send over the message bus. + * + * @param module the ldb_module + * @param action the transaction event i.e. begin, commit, roll back. + * @param log_level the logging level + * + */ +static void log_transaction( + struct ldb_module *module, + const char *action, + int log_level) +{ + + struct audit_private *audit_private = + talloc_get_type_abort(ldb_module_get_private(module), + struct audit_private); + const struct timeval now = timeval_current(); + const int64_t duration = usec_time_diff(&now, &audit_private->transaction_start); + + TALLOC_CTX *ctx = talloc_new(NULL); + + if (CHECK_DEBUGLVLC(DBGC_DSDB_TXN_AUDIT, log_level)) { + char* entry = NULL; + entry = transaction_human_readable(ctx, action, duration); + audit_log_human_text( + TRANSACTION_HR_TAG, + entry, + DBGC_DSDB_TXN_AUDIT, + log_level); + TALLOC_FREE(entry); + } + if (CHECK_DEBUGLVLC(DBGC_DSDB_TXN_AUDIT_JSON, log_level) || + (audit_private->msg_ctx && audit_private->send_samdb_events)) { + struct json_object json; + json = transaction_json( + action, + &audit_private->transaction_guid, + duration); + audit_log_json( + &json, + DBGC_DSDB_TXN_AUDIT_JSON, + log_level); + if (audit_private->send_samdb_events) { + audit_message_send( + audit_private->msg_ctx, + DSDB_EVENT_NAME, + MSG_DSDB_LOG, + &json); + } + json_free(&json); + } + TALLOC_FREE(ctx); +} + +/* + * @brief log details of a commit failure. + * + * Log the details of a commit failure in JSON and or human readable + * format and send over the message bus. + * + * @param module the ldb_module + * @param action the commit action "prepare" or "commit" + * @param status the ldb status code returned by prepare commit. + * + */ +static void log_commit_failure( + struct ldb_module *module, + const char *action, + int status) +{ + + struct audit_private *audit_private = + talloc_get_type_abort(ldb_module_get_private(module), + struct audit_private); + const char* reason = dsdb_audit_get_ldb_error_string(module, status); + const int log_level = TRANSACTION_LOG_FAILURE_LVL; + const struct timeval now = timeval_current(); + const int64_t duration = usec_time_diff(&now, + &audit_private->transaction_start); + + TALLOC_CTX *ctx = talloc_new(NULL); + + if (CHECK_DEBUGLVLC(DBGC_DSDB_TXN_AUDIT, log_level)) { + + char* entry = NULL; + entry = commit_failure_human_readable( + ctx, + action, + duration, + status, + reason); + audit_log_human_text( + TRANSACTION_HR_TAG, + entry, + DBGC_DSDB_TXN_AUDIT, + TRANSACTION_LOG_FAILURE_LVL); + TALLOC_FREE(entry); + } + if (CHECK_DEBUGLVLC(DBGC_DSDB_TXN_AUDIT_JSON, log_level) || + (audit_private->msg_ctx + && audit_private->send_samdb_events)) { + struct json_object json; + json = commit_failure_json( + action, + duration, + status, + reason, + &audit_private->transaction_guid); + audit_log_json( + &json, + DBGC_DSDB_TXN_AUDIT_JSON, + log_level); + if (audit_private->send_samdb_events) { + audit_message_send(audit_private->msg_ctx, + DSDB_EVENT_NAME, + MSG_DSDB_LOG, + &json); + } + json_free(&json); + } + TALLOC_FREE(ctx); +} + +/* + * Context needed by audit_callback + */ +struct audit_callback_context { + struct ldb_request *request; + struct ldb_module *module; +}; + +/* + * @brief call back function for the ldb_operations. + * + * As the LDB operations are async, and we wish to examine the results of + * the operations, a callback needs to be registered to process the results + * of the LDB operations. + * + * @param req the ldb request + * @param res the result of the operation + * + * @return the LDB_STATUS + */ +static int audit_callback(struct ldb_request *req, struct ldb_reply *ares) +{ + struct audit_callback_context *ac = NULL; + + ac = talloc_get_type( + req->context, + struct audit_callback_context); + + if (!ares) { + return ldb_module_done( + ac->request, + NULL, + NULL, + LDB_ERR_OPERATIONS_ERROR); + } + + /* pass on to the callback */ + switch (ares->type) { + case LDB_REPLY_ENTRY: + return ldb_module_send_entry( + ac->request, + ares->message, + ares->controls); + + case LDB_REPLY_REFERRAL: + return ldb_module_send_referral( + ac->request, + ares->referral); + + case LDB_REPLY_DONE: + /* + * Log the operation once DONE + */ + log_operation(ac->module, ac->request, ares); + return ldb_module_done( + ac->request, + ares->controls, + ares->response, + ares->error); + + default: + /* Can't happen */ + return LDB_ERR_OPERATIONS_ERROR; + } +} + +/* + * @brief Add the current transaction identifier to the request. + * + * Add the current transaction identifier in the module private data, + * to the request as a control. + * + * @param module + * @param req the request. + * + * @return an LDB_STATUS code, LDB_SUCCESS if successful. + */ +static int add_transaction_id( + struct ldb_module *module, + struct ldb_request *req) +{ + struct audit_private *audit_private = + talloc_get_type_abort(ldb_module_get_private(module), + struct audit_private); + struct dsdb_control_transaction_identifier *transaction_id; + int ret; + + transaction_id = talloc_zero( + req, + struct dsdb_control_transaction_identifier); + if (transaction_id == NULL) { + struct ldb_context *ldb = ldb_module_get_ctx(module); + return ldb_oom(ldb); + } + transaction_id->transaction_guid = audit_private->transaction_guid; + ret = ldb_request_add_control(req, + DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID, + false, + transaction_id); + return ret; + +} + +/* + * @brief log details of an add operation. + * + * Log the details of an add operation. + * + * @param module the ldb_module + * @param req the ldb_request + * + * @return ldb status code + */ +static int log_add( + struct ldb_module *module, + struct ldb_request *req) +{ + struct audit_callback_context *context = NULL; + struct ldb_request *new_req = NULL; + struct ldb_context *ldb = NULL; + int ret; + + ldb = ldb_module_get_ctx(module); + context = talloc_zero(req, struct audit_callback_context); + + if (context == NULL) { + return ldb_oom(ldb); + } + context->request = req; + context->module = module; + /* + * We want to log the return code status, so we need to register + * a callback function to get the actual result. + * We need to take a new copy so that we don't alter the callers copy + */ + ret = ldb_build_add_req( + &new_req, + ldb, + req, + req->op.add.message, + req->controls, + context, + audit_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + ret = add_transaction_id(module, new_req); + if (ret != LDB_SUCCESS) { + return ret; + } + return ldb_next_request(module, new_req); +} + +/* + * @brief log details of an delete operation. + * + * Log the details of an delete operation. + * + * @param module the ldb_module + * @param req the ldb_request + * + * @return ldb status code + */ +static int log_delete( + struct ldb_module *module, + struct ldb_request *req) +{ + struct audit_callback_context *context = NULL; + struct ldb_request *new_req = NULL; + struct ldb_context *ldb = NULL; + int ret; + + ldb = ldb_module_get_ctx(module); + context = talloc_zero(req, struct audit_callback_context); + + if (context == NULL) { + return ldb_oom(ldb); + } + context->request = req; + context->module = module; + /* + * We want to log the return code status, so we need to register + * a callback function to get the actual result. + * We need to take a new copy so that we don't alter the callers copy + */ + ret = ldb_build_del_req(&new_req, + ldb, + req, + req->op.del.dn, + req->controls, + context, + audit_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + ret = add_transaction_id(module, new_req); + if (ret != LDB_SUCCESS) { + return ret; + } + return ldb_next_request(module, new_req); +} + +/* + * @brief log details of a modify operation. + * + * Log the details of a modify operation. + * + * @param module the ldb_module + * @param req the ldb_request + * + * @return ldb status code + */ +static int log_modify( + struct ldb_module *module, + struct ldb_request *req) +{ + struct audit_callback_context *context = NULL; + struct ldb_request *new_req = NULL; + struct ldb_context *ldb = NULL; + int ret; + + ldb = ldb_module_get_ctx(module); + context = talloc_zero(req, struct audit_callback_context); + + if (context == NULL) { + return ldb_oom(ldb); + } + context->request = req; + context->module = module; + /* + * We want to log the return code status, so we need to register + * a callback function to get the actual result. + * We need to take a new copy so that we don't alter the callers copy + */ + ret = ldb_build_mod_req( + & new_req, + ldb, + req, + req->op.mod.message, + req->controls, + context, + audit_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + ret = add_transaction_id(module, new_req); + if (ret != LDB_SUCCESS) { + return ret; + } + return ldb_next_request(module, new_req); +} + +/* + * @brief process a transaction start. + * + * process a transaction start, as we don't currently log transaction starts + * just generate the new transaction_id. + * + * @param module the ldb_module + * @param req the ldb_request + * + * @return ldb status code + */ +static int log_start_transaction(struct ldb_module *module) +{ + struct audit_private *audit_private = + talloc_get_type_abort(ldb_module_get_private(module), + struct audit_private); + + /* + * We do not log transaction begins + * however we do generate a new transaction_id and record the start + * time so that we can log the transaction duration. + * + */ + audit_private->transaction_guid = GUID_random(); + audit_private->transaction_start = timeval_current(); + return ldb_next_start_trans(module); +} + +/* + * @brief log details of a prepare commit. + * + * Log the details of a prepare commit, currently only details of + * failures are logged. + * + * @param module the ldb_module + * @param req the ldb_request + * + * @return ldb status code + */ +static int log_prepare_commit(struct ldb_module *module) +{ + + int ret = ldb_next_prepare_commit(module); + if (ret != LDB_SUCCESS) { + /* + * We currently only log prepare commit failures + */ + log_commit_failure(module, "prepare", ret); + } + return ret; +} + +/* + * @brief process a transaction end aka commit. + * + * process a transaction end, as we don't currently log transaction ends + * just clear transaction_id. + * + * @param module the ldb_module + * @param req the ldb_request + * + * @return ldb status code + */ +static int log_end_transaction(struct ldb_module *module) +{ + struct audit_private *audit_private = + talloc_get_type_abort(ldb_module_get_private(module), + struct audit_private); + int ret = 0; + + + ret = ldb_next_end_trans(module); + if (ret == LDB_SUCCESS) { + log_transaction( + module, + "commit", + TRANSACTION_LOG_COMPLETION_LVL); + } else { + log_commit_failure(module, "commit", ret); + } + /* + * Clear the transaction id inserted by log_start_transaction + */ + audit_private->transaction_guid = GUID_zero(); + return ret; +} + +/* + * @brief log details of a transaction delete aka roll back. + * + * Log details of a transaction roll back. + * + * @param module the ldb_module + * @param req the ldb_request + * + * @return ldb status code + */ +static int log_del_transaction(struct ldb_module *module) +{ + struct audit_private *audit_private = + talloc_get_type_abort(ldb_module_get_private(module), + struct audit_private); + + log_transaction(module, "rollback", TRANSACTION_LOG_FAILURE_LVL); + audit_private->transaction_guid = GUID_zero(); + return ldb_next_del_trans(module); +} + +/* + * @brief log details of an extended operation. + * + * Log the details of an extended operation. + * + * @param module the ldb_module + * @param req the ldb_request + * + * @return ldb status code + */ +static int log_extended( + struct ldb_module *module, + struct ldb_request *req) +{ + struct audit_callback_context *context = NULL; + struct ldb_request *new_req = NULL; + struct ldb_context *ldb = NULL; + int ret; + + /* + * Currently we only log replication extended operations + */ + if (strcmp( + req->op.extended.oid, + DSDB_EXTENDED_REPLICATED_OBJECTS_OID) != 0) { + + return ldb_next_request(module, req); + } + ldb = ldb_module_get_ctx(module); + context = talloc_zero(req, struct audit_callback_context); + + if (context == NULL) { + return ldb_oom(ldb); + } + context->request = req; + context->module = module; + /* + * We want to log the return code status, so we need to register + * a callback function to get the actual result. + * We need to take a new copy so that we don't alter the callers copy + */ + ret = ldb_build_extended_req( + &new_req, + ldb, + req, + req->op.extended.oid, + req->op.extended.data, + req->controls, + context, + audit_callback, + req); + if (ret != LDB_SUCCESS) { + return ret; + } + ret = add_transaction_id(module, new_req); + if (ret != LDB_SUCCESS) { + return ret; + } + return ldb_next_request(module, new_req); +} + +/* + * @brief module initialisation + */ +static int log_init(struct ldb_module *module) +{ + + struct ldb_context *ldb = ldb_module_get_ctx(module); + struct audit_private *audit_private = NULL; + struct loadparm_context *lp_ctx + = talloc_get_type_abort(ldb_get_opaque(ldb, "loadparm"), + struct loadparm_context); + struct tevent_context *ev = ldb_get_event_context(ldb); + bool sdb_events = false; + bool pwd_events = false; + + audit_private = talloc_zero(module, struct audit_private); + if (audit_private == NULL) { + return ldb_module_oom(module); + } + + if (lp_ctx != NULL) { + sdb_events = lpcfg_dsdb_event_notification(lp_ctx); + pwd_events = lpcfg_dsdb_password_event_notification(lp_ctx); + } + if (sdb_events || pwd_events) { + audit_private->send_samdb_events = sdb_events; + audit_private->send_password_events = pwd_events; + audit_private->msg_ctx + = imessaging_client_init(audit_private, + lp_ctx, + ev); + } + + ldb_module_set_private(module, audit_private); + return ldb_next_init(module); +} + +static const struct ldb_module_ops ldb_audit_log_module_ops = { + .name = "audit_log", + .init_context = log_init, + .add = log_add, + .modify = log_modify, + .del = log_delete, + .start_transaction = log_start_transaction, + .prepare_commit = log_prepare_commit, + .end_transaction = log_end_transaction, + .del_transaction = log_del_transaction, + .extended = log_extended, +}; + +int ldb_audit_log_module_init(const char *version) +{ + LDB_MODULE_CHECK_VERSION(version); + return ldb_register_module(&ldb_audit_log_module_ops); +} |