1
0
mirror of https://github.com/samba-team/samba.git synced 2025-02-03 13:47:25 +03:00
Volker Lendecke f53c8fbd7f audit_log: Align integer types
Signed-off-by: Volker Lendecke <vl@samba.org>
Reviewed-by: David Disseldorp <ddiss@samba.org>
2020-01-03 00:04:43 +00:00

1914 lines
47 KiB
C

/*
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);
}