mirror of
https://github.com/samba-team/samba.git
synced 2025-02-03 13:47:25 +03:00
d2a08f5d67
When the "(status == LDB_SUCCESS && msg != NULL)" condition in this routine is not evaluating to true, "new_rid" is read uninitialized, comparing it against ~0. Initialize new_rid and compare it against UINT32_MAX instead of ~0. Signed-off-by: Volker Lendecke <vl@samba.org> Reviewed-by: Jeremy Allison <jra@samba.org>
1556 lines
38 KiB
C
1556 lines
38 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 group memberships
|
|
*
|
|
*/
|
|
|
|
#include "includes.h"
|
|
#include "ldb_module.h"
|
|
#include "lib/audit_logging/audit_logging.h"
|
|
#include "librpc/gen_ndr/windows_event_ids.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"
|
|
|
|
#define AUDIT_JSON_TYPE "groupChange"
|
|
#define AUDIT_HR_TAG "Group Change"
|
|
#define AUDIT_MAJOR 1
|
|
#define AUDIT_MINOR 1
|
|
#define GROUP_LOG_LVL 5
|
|
|
|
static const char *const group_attrs[] = {"member", "groupType", NULL};
|
|
static const char *const group_type_attr[] = {"groupType", NULL};
|
|
static const char * const primary_group_attr[] = {
|
|
"primaryGroupID",
|
|
"objectSID",
|
|
NULL};
|
|
|
|
struct audit_context {
|
|
bool send_events;
|
|
struct imessaging_context *msg_ctx;
|
|
};
|
|
|
|
struct audit_callback_context {
|
|
struct ldb_request *request;
|
|
struct ldb_module *module;
|
|
struct ldb_message_element *members;
|
|
uint32_t primary_group;
|
|
void (*log_changes)(
|
|
struct audit_callback_context *acc,
|
|
const int status);
|
|
};
|
|
|
|
/*
|
|
* @brief get the transaction id.
|
|
*
|
|
* Get the id of the transaction that the current request is contained in.
|
|
*
|
|
* @param req the request.
|
|
*
|
|
* @return the transaction id GUID, or NULL if it is not there.
|
|
*/
|
|
static struct GUID *get_transaction_id(
|
|
const struct ldb_request *request)
|
|
{
|
|
struct ldb_control *control;
|
|
struct dsdb_control_transaction_identifier *transaction_id;
|
|
|
|
control = ldb_request_get_control(
|
|
discard_const(request),
|
|
DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID);
|
|
if (control == NULL) {
|
|
return NULL;
|
|
}
|
|
transaction_id = talloc_get_type(
|
|
control->data,
|
|
struct dsdb_control_transaction_identifier);
|
|
if (transaction_id == NULL) {
|
|
return NULL;
|
|
}
|
|
return &transaction_id->transaction_guid;
|
|
}
|
|
|
|
/*
|
|
* @brief generate a JSON log entry for a group change.
|
|
*
|
|
* Generate a JSON object containing details of a users group change.
|
|
*
|
|
* @param module the ldb module
|
|
* @param request the ldb_request
|
|
* @param action the change action being performed
|
|
* @param user the user name
|
|
* @param group the group name
|
|
* @param status the ldb status code for the ldb operation.
|
|
*
|
|
* @return A json object containing the details.
|
|
* NULL if an error was detected
|
|
*/
|
|
static struct json_object audit_group_json(const struct ldb_module *module,
|
|
const struct ldb_request *request,
|
|
const char *action,
|
|
const char *user,
|
|
const char *group,
|
|
const enum event_id_type event_id,
|
|
const int status)
|
|
{
|
|
struct ldb_context *ldb = NULL;
|
|
const struct dom_sid *sid = NULL;
|
|
struct json_object wrapper = json_empty_object;
|
|
struct json_object audit = json_empty_object;
|
|
const struct tsocket_address *remote = NULL;
|
|
const struct GUID *unique_session_token = NULL;
|
|
struct GUID *transaction_id = NULL;
|
|
int rc = 0;
|
|
|
|
ldb = ldb_module_get_ctx(discard_const(module));
|
|
|
|
remote = dsdb_audit_get_remote_address(ldb);
|
|
sid = dsdb_audit_get_user_sid(module);
|
|
unique_session_token = dsdb_audit_get_unique_session_token(module);
|
|
transaction_id = get_transaction_id(request);
|
|
|
|
audit = json_new_object();
|
|
if (json_is_invalid(&audit)) {
|
|
goto failure;
|
|
}
|
|
rc = json_add_version(&audit, AUDIT_MAJOR, AUDIT_MINOR);
|
|
if (rc != 0) {
|
|
goto failure;
|
|
}
|
|
if (event_id != EVT_ID_NONE) {
|
|
rc = json_add_int(&audit, "eventId", event_id);
|
|
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, "action", action);
|
|
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, "group", group);
|
|
if (rc != 0) {
|
|
goto failure;
|
|
}
|
|
rc = json_add_guid(&audit, "transactionId", transaction_id);
|
|
if (rc != 0) {
|
|
goto failure;
|
|
}
|
|
rc = json_add_guid(&audit, "sessionId", unique_session_token);
|
|
if (rc != 0) {
|
|
goto failure;
|
|
}
|
|
rc = json_add_string(&audit, "user", user);
|
|
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", AUDIT_JSON_TYPE);
|
|
if (rc != 0) {
|
|
goto failure;
|
|
}
|
|
rc = json_add_object(&wrapper, AUDIT_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("Failed to create group change JSON log message\n");
|
|
return wrapper;
|
|
}
|
|
|
|
/*
|
|
* @brief generate a human readable log entry for a group change.
|
|
*
|
|
* Generate a human readable log entry containing details of a users group
|
|
* change.
|
|
*
|
|
* @param ctx the talloc context owning the returned log entry
|
|
* @param module the ldb module
|
|
* @param request the ldb_request
|
|
* @param action the change action being performed
|
|
* @param user the user name
|
|
* @param group the group name
|
|
* @param status the ldb status code for the ldb operation.
|
|
*
|
|
* @return A human readable log line.
|
|
*/
|
|
static char *audit_group_human_readable(
|
|
TALLOC_CTX *mem_ctx,
|
|
const struct ldb_module *module,
|
|
const struct ldb_request *request,
|
|
const char *action,
|
|
const char *user,
|
|
const char *group,
|
|
const int status)
|
|
{
|
|
struct ldb_context *ldb = NULL;
|
|
const char *remote_host = NULL;
|
|
const struct dom_sid *sid = NULL;
|
|
const char *user_sid = NULL;
|
|
const char *timestamp = NULL;
|
|
char *log_entry = NULL;
|
|
|
|
TALLOC_CTX *ctx = talloc_new(NULL);
|
|
|
|
ldb = ldb_module_get_ctx(discard_const(module));
|
|
|
|
remote_host = dsdb_audit_get_remote_host(ldb, ctx);
|
|
sid = dsdb_audit_get_user_sid(module);
|
|
user_sid = dom_sid_string(ctx, sid);
|
|
timestamp = audit_get_timestamp(ctx);
|
|
|
|
log_entry = talloc_asprintf(
|
|
mem_ctx,
|
|
"[%s] at [%s] status [%s] "
|
|
"Remote host [%s] SID [%s] Group [%s] User [%s]",
|
|
action,
|
|
timestamp,
|
|
ldb_strerror(status),
|
|
remote_host,
|
|
user_sid,
|
|
group,
|
|
user);
|
|
TALLOC_FREE(ctx);
|
|
return log_entry;
|
|
}
|
|
|
|
/*
|
|
* @brief generate an array of parsed_dns, deferring the actual parsing.
|
|
*
|
|
* Get an array of 'struct parsed_dns' without the parsing.
|
|
* The parsed_dns are parsed only when needed to avoid the expense of parsing.
|
|
*
|
|
* This procedure assumes that the dn's are sorted in GUID order and contains
|
|
* no duplicates. This should be valid as the module sits below repl_meta_data
|
|
* which ensures this.
|
|
*
|
|
* @param mem_ctx The memory context that will own the generated array
|
|
* @param el The message element used to generate the array.
|
|
*
|
|
* @return an array of struct parsed_dns, or NULL in the event of an error
|
|
*/
|
|
static struct parsed_dn *get_parsed_dns(
|
|
TALLOC_CTX *mem_ctx,
|
|
struct ldb_message_element *el)
|
|
{
|
|
int ret;
|
|
struct parsed_dn *pdn = NULL;
|
|
|
|
if (el == NULL || el->num_values == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
ret = get_parsed_dns_trusted(mem_ctx, el, &pdn);
|
|
if (ret == LDB_ERR_OPERATIONS_ERROR) {
|
|
DBG_ERR("Out of memory\n");
|
|
return NULL;
|
|
}
|
|
return pdn;
|
|
|
|
}
|
|
|
|
enum dn_compare_result {
|
|
LESS_THAN,
|
|
BINARY_EQUAL,
|
|
EQUAL,
|
|
GREATER_THAN
|
|
};
|
|
/*
|
|
* @brief compare parsed_dn, using GUID ordering
|
|
*
|
|
* Compare two parsed_dn structures, using GUID ordering.
|
|
* To avoid the overhead of parsing the DN's this function does a binary
|
|
* compare first. The DN's tre only parsed if they are not equal at a binary
|
|
* level.
|
|
*
|
|
* @param ctx talloc context that will own the parsed dsdb_dn
|
|
* @param ldb ldb_context
|
|
* @param dn1 The first dn
|
|
* @param dn2 The second dn
|
|
*
|
|
* @return BINARY_EQUAL values are equal at a binary level
|
|
* EQUAL DN's are equal but the meta data is different
|
|
* LESS_THAN dn1's GUID is less than dn2's GUID
|
|
* GREATER_THAN dn1's GUID is greater than dn2's GUID
|
|
*
|
|
*/
|
|
static enum dn_compare_result dn_compare(
|
|
TALLOC_CTX *mem_ctx,
|
|
struct ldb_context *ldb,
|
|
struct parsed_dn *dn1,
|
|
struct parsed_dn *dn2) {
|
|
|
|
int res = 0;
|
|
|
|
/*
|
|
* Do a binary compare first to avoid unnecessary parsing
|
|
*/
|
|
if (data_blob_cmp(dn1->v, dn2->v) == 0) {
|
|
/*
|
|
* Values are equal at a binary level so no need
|
|
* for further processing
|
|
*/
|
|
return BINARY_EQUAL;
|
|
}
|
|
/*
|
|
* Values not equal at the binary level, so lets
|
|
* do a GUID ordering compare. To do this we will need to ensure
|
|
* that the dn's have been parsed.
|
|
*/
|
|
if (dn1->dsdb_dn == NULL) {
|
|
really_parse_trusted_dn(
|
|
mem_ctx,
|
|
ldb,
|
|
dn1,
|
|
LDB_SYNTAX_DN);
|
|
}
|
|
if (dn2->dsdb_dn == NULL) {
|
|
really_parse_trusted_dn(
|
|
mem_ctx,
|
|
ldb,
|
|
dn2,
|
|
LDB_SYNTAX_DN);
|
|
}
|
|
|
|
res = ndr_guid_compare(&dn1->guid, &dn2->guid);
|
|
if (res < 0) {
|
|
return LESS_THAN;
|
|
} else if (res == 0) {
|
|
return EQUAL;
|
|
} else {
|
|
return GREATER_THAN;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @brief Get the DN of a users primary group as a printable string.
|
|
*
|
|
* Get the DN of a users primary group as a printable string.
|
|
*
|
|
* @param mem_ctx Talloc context the the returned string will be allocated on.
|
|
* @param module The ldb module
|
|
* @param account_sid The SID for the uses account.
|
|
* @param primary_group_rid The RID for the users primary group.
|
|
*
|
|
* @return a formatted DN, or null if there is an error.
|
|
*/
|
|
static const char *get_primary_group_dn(
|
|
TALLOC_CTX *mem_ctx,
|
|
struct ldb_module *module,
|
|
struct dom_sid *account_sid,
|
|
uint32_t primary_group_rid)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
struct ldb_context *ldb = NULL;
|
|
struct dom_sid *domain_sid = NULL;
|
|
struct dom_sid *primary_group_sid = NULL;
|
|
char *sid = NULL;
|
|
struct ldb_dn *dn = NULL;
|
|
struct ldb_message *msg = NULL;
|
|
int rc;
|
|
|
|
ldb = ldb_module_get_ctx(module);
|
|
|
|
status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
return NULL;
|
|
}
|
|
|
|
primary_group_sid = dom_sid_add_rid(
|
|
mem_ctx,
|
|
domain_sid,
|
|
primary_group_rid);
|
|
if (!primary_group_sid) {
|
|
return NULL;
|
|
}
|
|
|
|
sid = dom_sid_string(mem_ctx, primary_group_sid);
|
|
if (sid == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
dn = ldb_dn_new_fmt(mem_ctx, ldb, "<SID=%s>", sid);
|
|
if(dn == NULL) {
|
|
return sid;
|
|
}
|
|
rc = dsdb_search_one(
|
|
ldb,
|
|
mem_ctx,
|
|
&msg,
|
|
dn,
|
|
LDB_SCOPE_BASE,
|
|
NULL,
|
|
0,
|
|
NULL);
|
|
if (rc != LDB_SUCCESS) {
|
|
return NULL;
|
|
}
|
|
|
|
return ldb_dn_get_linearized(msg->dn);
|
|
}
|
|
|
|
/*
|
|
* @brief Log details of a change to a users primary group.
|
|
*
|
|
* Log details of a change to a users primary group.
|
|
* There is no windows event id associated with a Primary Group change.
|
|
* However for a new user we generate an added to group event.
|
|
*
|
|
* @param module The ldb module.
|
|
* @param request The request being logged.
|
|
* @param action Description of the action being performed.
|
|
* @param group The linearized for of the group DN
|
|
* @param status the LDB status code for the processing of the request.
|
|
*
|
|
*/
|
|
static void log_primary_group_change(
|
|
struct ldb_module *module,
|
|
const struct ldb_request *request,
|
|
const char *action,
|
|
const char *group,
|
|
const int status)
|
|
{
|
|
const char *user = NULL;
|
|
|
|
struct audit_context *ac =
|
|
talloc_get_type(
|
|
ldb_module_get_private(module),
|
|
struct audit_context);
|
|
|
|
TALLOC_CTX *ctx = talloc_new(NULL);
|
|
|
|
user = dsdb_audit_get_primary_dn(request);
|
|
if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL)) {
|
|
char *message = NULL;
|
|
message = audit_group_human_readable(
|
|
ctx,
|
|
module,
|
|
request,
|
|
action,
|
|
user,
|
|
group,
|
|
status);
|
|
audit_log_human_text(
|
|
AUDIT_HR_TAG,
|
|
message,
|
|
DBGC_DSDB_GROUP_AUDIT,
|
|
GROUP_LOG_LVL);
|
|
TALLOC_FREE(message);
|
|
}
|
|
|
|
if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
|
|
(ac->msg_ctx && ac->send_events)) {
|
|
|
|
struct json_object json;
|
|
json = audit_group_json(
|
|
module, request, action, user, group, EVT_ID_NONE, status);
|
|
audit_log_json(
|
|
&json,
|
|
DBGC_DSDB_GROUP_AUDIT_JSON,
|
|
GROUP_LOG_LVL);
|
|
if (ac->send_events) {
|
|
audit_message_send(
|
|
ac->msg_ctx,
|
|
DSDB_GROUP_EVENT_NAME,
|
|
MSG_GROUP_LOG,
|
|
&json);
|
|
}
|
|
json_free(&json);
|
|
if (request->operation == LDB_ADD) {
|
|
/*
|
|
* Have just added a user, generate a groupChange
|
|
* message indicating the user has been added to thier
|
|
* new PrimaryGroup.
|
|
*/
|
|
}
|
|
}
|
|
TALLOC_FREE(ctx);
|
|
}
|
|
|
|
/*
|
|
* @brief Log details of a single change to a users group membership.
|
|
*
|
|
* Log details of a change to a users group membership, except for changes
|
|
* to their primary group which is handled by log_primary_group_change.
|
|
*
|
|
* @param module The ldb module.
|
|
* @param request The request being logged.
|
|
* @param action Description of the action being performed.
|
|
* @param user The linearized form of the users DN
|
|
* @param status the LDB status code for the processing of the request.
|
|
*
|
|
*/
|
|
static void log_membership_change(struct ldb_module *module,
|
|
const struct ldb_request *request,
|
|
const char *action,
|
|
const char *user,
|
|
const enum event_id_type event_id,
|
|
const int status)
|
|
{
|
|
const char *group = NULL;
|
|
struct audit_context *ac =
|
|
talloc_get_type(
|
|
ldb_module_get_private(module),
|
|
struct audit_context);
|
|
|
|
TALLOC_CTX *ctx = talloc_new(NULL);
|
|
group = dsdb_audit_get_primary_dn(request);
|
|
if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL)) {
|
|
char *message = NULL;
|
|
message = audit_group_human_readable(
|
|
ctx,
|
|
module,
|
|
request,
|
|
action,
|
|
user,
|
|
group,
|
|
status);
|
|
audit_log_human_text(
|
|
AUDIT_HR_TAG,
|
|
message,
|
|
DBGC_DSDB_GROUP_AUDIT,
|
|
GROUP_LOG_LVL);
|
|
TALLOC_FREE(message);
|
|
}
|
|
|
|
if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
|
|
(ac->msg_ctx && ac->send_events)) {
|
|
struct json_object json;
|
|
json = audit_group_json(
|
|
module, request, action, user, group, event_id, status);
|
|
audit_log_json(
|
|
&json,
|
|
DBGC_DSDB_GROUP_AUDIT_JSON,
|
|
GROUP_LOG_LVL);
|
|
if (ac->send_events) {
|
|
audit_message_send(
|
|
ac->msg_ctx,
|
|
DSDB_GROUP_EVENT_NAME,
|
|
MSG_GROUP_LOG,
|
|
&json);
|
|
}
|
|
json_free(&json);
|
|
}
|
|
TALLOC_FREE(ctx);
|
|
}
|
|
|
|
/*
|
|
* @brief Get the windows event type id for removing a user from a group type.
|
|
*
|
|
* @param group_type the type of the current group, see libds/common/flags.h
|
|
*
|
|
* @return the Windows Event Id
|
|
*
|
|
*/
|
|
static enum event_id_type get_remove_member_event(uint32_t group_type)
|
|
{
|
|
|
|
switch (group_type) {
|
|
case GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
|
|
return EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP;
|
|
case GTYPE_SECURITY_GLOBAL_GROUP:
|
|
return EVT_ID_USER_REMOVED_FROM_GLOBAL_SEC_GROUP;
|
|
case GTYPE_SECURITY_DOMAIN_LOCAL_GROUP:
|
|
return EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP;
|
|
case GTYPE_SECURITY_UNIVERSAL_GROUP:
|
|
return EVT_ID_USER_REMOVED_FROM_UNIVERSAL_SEC_GROUP;
|
|
case GTYPE_DISTRIBUTION_GLOBAL_GROUP:
|
|
return EVT_ID_USER_REMOVED_FROM_GLOBAL_GROUP;
|
|
case GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP:
|
|
return EVT_ID_USER_REMOVED_FROM_LOCAL_GROUP;
|
|
case GTYPE_DISTRIBUTION_UNIVERSAL_GROUP:
|
|
return EVT_ID_USER_REMOVED_FROM_UNIVERSAL_GROUP;
|
|
default:
|
|
return EVT_ID_NONE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @brief Get the windows event type id for adding a user to a group type.
|
|
*
|
|
* @param group_type the type of the current group, see libds/common/flags.h
|
|
*
|
|
* @return the Windows Event Id
|
|
*
|
|
*/
|
|
static enum event_id_type get_add_member_event(uint32_t group_type)
|
|
{
|
|
|
|
switch (group_type) {
|
|
case GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
|
|
return EVT_ID_USER_ADDED_TO_LOCAL_SEC_GROUP;
|
|
case GTYPE_SECURITY_GLOBAL_GROUP:
|
|
return EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP;
|
|
case GTYPE_SECURITY_DOMAIN_LOCAL_GROUP:
|
|
return EVT_ID_USER_ADDED_TO_LOCAL_SEC_GROUP;
|
|
case GTYPE_SECURITY_UNIVERSAL_GROUP:
|
|
return EVT_ID_USER_ADDED_TO_UNIVERSAL_SEC_GROUP;
|
|
case GTYPE_DISTRIBUTION_GLOBAL_GROUP:
|
|
return EVT_ID_USER_ADDED_TO_GLOBAL_GROUP;
|
|
case GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP:
|
|
return EVT_ID_USER_ADDED_TO_LOCAL_GROUP;
|
|
case GTYPE_DISTRIBUTION_UNIVERSAL_GROUP:
|
|
return EVT_ID_USER_ADDED_TO_UNIVERSAL_GROUP;
|
|
default:
|
|
return EVT_ID_NONE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @brief Log all the changes to a users group membership.
|
|
*
|
|
* Log details of a change to a users group memberships, except for changes
|
|
* to their primary group which is handled by log_primary_group_change.
|
|
*
|
|
* @param module The ldb module.
|
|
* @param request The request being logged.
|
|
* @param action Description of the action being performed.
|
|
* @param user The linearized form of the users DN
|
|
* @param status the LDB status code for the processing of the request.
|
|
*
|
|
*/
|
|
static void log_membership_changes(struct ldb_module *module,
|
|
const struct ldb_request *request,
|
|
struct ldb_message_element *el,
|
|
struct ldb_message_element *old_el,
|
|
uint32_t group_type,
|
|
int status)
|
|
{
|
|
unsigned int i, old_i, new_i;
|
|
unsigned int old_num_values;
|
|
unsigned int max_num_values;
|
|
unsigned int new_num_values;
|
|
struct parsed_dn *old_val = NULL;
|
|
struct parsed_dn *new_val = NULL;
|
|
struct parsed_dn *new_values = NULL;
|
|
struct parsed_dn *old_values = NULL;
|
|
struct ldb_context *ldb = NULL;
|
|
|
|
TALLOC_CTX *ctx = talloc_new(NULL);
|
|
|
|
old_num_values = old_el ? old_el->num_values : 0;
|
|
new_num_values = el ? el->num_values : 0;
|
|
max_num_values = old_num_values + new_num_values;
|
|
|
|
if (max_num_values == 0) {
|
|
/*
|
|
* There is nothing to do!
|
|
*/
|
|
TALLOC_FREE(ctx);
|
|
return;
|
|
}
|
|
|
|
old_values = get_parsed_dns(ctx, old_el);
|
|
new_values = get_parsed_dns(ctx, el);
|
|
ldb = ldb_module_get_ctx(module);
|
|
|
|
old_i = 0;
|
|
new_i = 0;
|
|
for (i = 0; i < max_num_values; i++) {
|
|
enum dn_compare_result cmp;
|
|
if (old_i < old_num_values && new_i < new_num_values) {
|
|
/*
|
|
* Both list have values, so compare the values
|
|
*/
|
|
old_val = &old_values[old_i];
|
|
new_val = &new_values[new_i];
|
|
cmp = dn_compare(ctx, ldb, old_val, new_val);
|
|
} else if (old_i < old_num_values) {
|
|
/*
|
|
* the new list is empty, read the old list
|
|
*/
|
|
old_val = &old_values[old_i];
|
|
new_val = NULL;
|
|
cmp = LESS_THAN;
|
|
} else if (new_i < new_num_values) {
|
|
/*
|
|
* the old list is empty, read new list
|
|
*/
|
|
old_val = NULL;
|
|
new_val = &new_values[new_i];
|
|
cmp = GREATER_THAN;
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
if (cmp == LESS_THAN) {
|
|
/*
|
|
* Have an entry in the original record that is not in
|
|
* the new record. So it's been deleted
|
|
*/
|
|
const char *user = NULL;
|
|
enum event_id_type event_id;
|
|
if (old_val->dsdb_dn == NULL) {
|
|
really_parse_trusted_dn(
|
|
ctx,
|
|
ldb,
|
|
old_val,
|
|
LDB_SYNTAX_DN);
|
|
}
|
|
user = ldb_dn_get_linearized(old_val->dsdb_dn->dn);
|
|
event_id = get_remove_member_event(group_type);
|
|
log_membership_change(
|
|
module, request, "Removed", user, event_id, status);
|
|
old_i++;
|
|
} else if (cmp == BINARY_EQUAL) {
|
|
/*
|
|
* DN's unchanged at binary level so nothing to do.
|
|
*/
|
|
old_i++;
|
|
new_i++;
|
|
} else if (cmp == EQUAL) {
|
|
/*
|
|
* DN is unchanged now need to check the flags to
|
|
* determine if a record has been deleted or undeleted
|
|
*/
|
|
uint32_t old_flags;
|
|
uint32_t new_flags;
|
|
if (old_val->dsdb_dn == NULL) {
|
|
really_parse_trusted_dn(
|
|
ctx,
|
|
ldb,
|
|
old_val,
|
|
LDB_SYNTAX_DN);
|
|
}
|
|
if (new_val->dsdb_dn == NULL) {
|
|
really_parse_trusted_dn(
|
|
ctx,
|
|
ldb,
|
|
new_val,
|
|
LDB_SYNTAX_DN);
|
|
}
|
|
|
|
dsdb_get_extended_dn_uint32(
|
|
old_val->dsdb_dn->dn,
|
|
&old_flags,
|
|
"RMD_FLAGS");
|
|
dsdb_get_extended_dn_uint32(
|
|
new_val->dsdb_dn->dn,
|
|
&new_flags,
|
|
"RMD_FLAGS");
|
|
if (new_flags == old_flags) {
|
|
/*
|
|
* No changes to the Repl meta data so can
|
|
* no need to log the change
|
|
*/
|
|
old_i++;
|
|
new_i++;
|
|
continue;
|
|
}
|
|
if (new_flags & DSDB_RMD_FLAG_DELETED) {
|
|
/*
|
|
* DN has been deleted.
|
|
*/
|
|
const char *user = NULL;
|
|
enum event_id_type event_id;
|
|
user = ldb_dn_get_linearized(
|
|
old_val->dsdb_dn->dn);
|
|
event_id = get_remove_member_event(group_type);
|
|
log_membership_change(module,
|
|
request,
|
|
"Removed",
|
|
user,
|
|
event_id,
|
|
status);
|
|
} else {
|
|
/*
|
|
* DN has been re-added
|
|
*/
|
|
const char *user = NULL;
|
|
enum event_id_type event_id;
|
|
user = ldb_dn_get_linearized(
|
|
new_val->dsdb_dn->dn);
|
|
event_id = get_add_member_event(group_type);
|
|
log_membership_change(module,
|
|
request,
|
|
"Added",
|
|
user,
|
|
event_id,
|
|
status);
|
|
}
|
|
old_i++;
|
|
new_i++;
|
|
} else {
|
|
/*
|
|
* Member in the updated record that's not in the
|
|
* original, so it must have been added.
|
|
*/
|
|
const char *user = NULL;
|
|
enum event_id_type event_id;
|
|
if ( new_val->dsdb_dn == NULL) {
|
|
really_parse_trusted_dn(
|
|
ctx,
|
|
ldb,
|
|
new_val,
|
|
LDB_SYNTAX_DN);
|
|
}
|
|
user = ldb_dn_get_linearized(new_val->dsdb_dn->dn);
|
|
event_id = get_add_member_event(group_type);
|
|
log_membership_change(
|
|
module, request, "Added", user, event_id, status);
|
|
new_i++;
|
|
}
|
|
}
|
|
|
|
TALLOC_FREE(ctx);
|
|
}
|
|
|
|
/*
|
|
* @brief log a group change message for a newly added user.
|
|
*
|
|
* When a user is added we need to generate a GroupChange Add message to
|
|
* log that the user has been added to their PrimaryGroup
|
|
*/
|
|
static void log_new_user_added_to_primary_group(
|
|
TALLOC_CTX *ctx,
|
|
struct audit_callback_context *acc,
|
|
const char *group,
|
|
const int status)
|
|
{
|
|
uint32_t group_type;
|
|
enum event_id_type event_id = EVT_ID_NONE;
|
|
struct ldb_result *res = NULL;
|
|
struct ldb_dn *group_dn = NULL;
|
|
struct ldb_context *ldb = NULL;
|
|
int ret;
|
|
|
|
ldb = ldb_module_get_ctx(acc->module);
|
|
group_dn = ldb_dn_new(ctx, ldb, group);
|
|
ret = dsdb_module_search_dn(acc->module,
|
|
ctx,
|
|
&res,
|
|
group_dn,
|
|
group_type_attr,
|
|
DSDB_FLAG_NEXT_MODULE |
|
|
DSDB_SEARCH_REVEAL_INTERNALS |
|
|
DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
|
|
NULL);
|
|
if (ret == LDB_SUCCESS) {
|
|
const char *user = NULL;
|
|
group_type =
|
|
ldb_msg_find_attr_as_uint(res->msgs[0], "groupType", 0);
|
|
event_id = get_add_member_event(group_type);
|
|
user = dsdb_audit_get_primary_dn(acc->request);
|
|
log_membership_change(
|
|
acc->module, acc->request, "Added", user, event_id, status);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @brief Log the details of a primary group change.
|
|
*
|
|
* Retrieve the users primary groupo after the operation has completed
|
|
* and call log_primary_group_change to log the actual changes.
|
|
*
|
|
* @param acc details of the primary group before the operation.
|
|
* @param status The status code returned by the operation.
|
|
*
|
|
* @return an LDB status code.
|
|
*/
|
|
static void log_user_primary_group_change(
|
|
struct audit_callback_context *acc,
|
|
const int status)
|
|
{
|
|
TALLOC_CTX *ctx = talloc_new(NULL);
|
|
uint32_t new_rid = UINT32_MAX;
|
|
struct dom_sid *account_sid = NULL;
|
|
int ret;
|
|
const struct ldb_message *msg = dsdb_audit_get_message(acc->request);
|
|
|
|
if (status == LDB_SUCCESS && msg != NULL) {
|
|
struct ldb_result *res = NULL;
|
|
ret = dsdb_module_search_dn(
|
|
acc->module,
|
|
ctx,
|
|
&res,
|
|
msg->dn,
|
|
primary_group_attr,
|
|
DSDB_FLAG_NEXT_MODULE |
|
|
DSDB_SEARCH_REVEAL_INTERNALS |
|
|
DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
|
|
NULL);
|
|
if (ret == LDB_SUCCESS) {
|
|
new_rid = ldb_msg_find_attr_as_uint(
|
|
msg,
|
|
"primaryGroupID",
|
|
~0);
|
|
account_sid = samdb_result_dom_sid(
|
|
ctx,
|
|
res->msgs[0],
|
|
"objectSid");
|
|
}
|
|
}
|
|
/*
|
|
* If we don't have a new value then the user has been deleted
|
|
* which we currently do not log.
|
|
* Otherwise only log if the primary group has actually changed.
|
|
*/
|
|
if (account_sid != NULL &&
|
|
new_rid != UINT32_MAX &&
|
|
acc->primary_group != new_rid) {
|
|
const char* group = get_primary_group_dn(
|
|
ctx,
|
|
acc->module,
|
|
account_sid,
|
|
new_rid);
|
|
log_primary_group_change(
|
|
acc->module,
|
|
acc->request,
|
|
"PrimaryGroup",
|
|
group,
|
|
status);
|
|
/*
|
|
* Are we adding a new user with the primaryGroupID
|
|
* set. If so and we're generating JSON audit logs, will need to
|
|
* generate an "Add" message with the appropriate windows
|
|
* event id.
|
|
*/
|
|
if (acc->request->operation == LDB_ADD) {
|
|
log_new_user_added_to_primary_group(
|
|
ctx, acc, group, status);
|
|
}
|
|
}
|
|
TALLOC_FREE(ctx);
|
|
}
|
|
|
|
/*
|
|
* @brief log the changes to users group membership.
|
|
*
|
|
* Retrieve the users group memberships after the operation has completed
|
|
* and call log_membership_changes to log the actual changes.
|
|
*
|
|
* @param acc details of the group memberships before the operation.
|
|
* @param status The status code returned by the operation.
|
|
*
|
|
*/
|
|
static void log_group_membership_changes(
|
|
struct audit_callback_context *acc,
|
|
const int status)
|
|
{
|
|
TALLOC_CTX *ctx = talloc_new(NULL);
|
|
struct ldb_message_element *new_val = NULL;
|
|
int ret;
|
|
uint32_t group_type = 0;
|
|
const struct ldb_message *msg = dsdb_audit_get_message(acc->request);
|
|
if (status == LDB_SUCCESS && msg != NULL) {
|
|
struct ldb_result *res = NULL;
|
|
ret = dsdb_module_search_dn(
|
|
acc->module,
|
|
ctx,
|
|
&res,
|
|
msg->dn,
|
|
group_attrs,
|
|
DSDB_FLAG_NEXT_MODULE |
|
|
DSDB_SEARCH_REVEAL_INTERNALS |
|
|
DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
|
|
NULL);
|
|
if (ret == LDB_SUCCESS) {
|
|
new_val = ldb_msg_find_element(res->msgs[0], "member");
|
|
group_type = ldb_msg_find_attr_as_uint(
|
|
res->msgs[0], "groupType", 0);
|
|
log_membership_changes(acc->module,
|
|
acc->request,
|
|
new_val,
|
|
acc->members,
|
|
group_type,
|
|
status);
|
|
TALLOC_FREE(ctx);
|
|
return;
|
|
}
|
|
}
|
|
/*
|
|
* If we get here either
|
|
* one of the lower level modules failed and the group record did
|
|
* not get updated
|
|
* or
|
|
* the updated group record could not be read.
|
|
*
|
|
* In both cases it does not make sense to log individual membership
|
|
* changes so we log a group membership change "Failure" message.
|
|
*
|
|
*/
|
|
log_membership_change(acc->module,
|
|
acc->request,
|
|
"Failure",
|
|
"",
|
|
EVT_ID_NONE,
|
|
status);
|
|
TALLOC_FREE(ctx);
|
|
}
|
|
|
|
/*
|
|
* @brief call back function to log changes to the group memberships.
|
|
*
|
|
* Call back function to log changes to the uses broup memberships.
|
|
*
|
|
* @param req the ldb request.
|
|
* @param ares the ldb result
|
|
*
|
|
* @return am LDB status code.
|
|
*/
|
|
static int group_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 on DONE now we have a result code
|
|
*/
|
|
ac->log_changes(ac, ares->error);
|
|
return ldb_module_done(
|
|
ac->request,
|
|
ares->controls,
|
|
ares->response,
|
|
ares->error);
|
|
break;
|
|
|
|
default:
|
|
/* Can't happen */
|
|
return LDB_ERR_OPERATIONS_ERROR;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @brief Does this request change the primary group.
|
|
*
|
|
* Does the request change the primary group, i.e. does it contain the
|
|
* primaryGroupID attribute.
|
|
*
|
|
* @param req the request to examine.
|
|
*
|
|
* @return True if the request modifies the primary group.
|
|
*/
|
|
static bool has_primary_group_id(struct ldb_request *req)
|
|
{
|
|
struct ldb_message_element *el = NULL;
|
|
const struct ldb_message *msg = NULL;
|
|
|
|
msg = dsdb_audit_get_message(req);
|
|
el = ldb_msg_find_element(msg, "primaryGroupID");
|
|
|
|
return (el != NULL);
|
|
}
|
|
|
|
/*
|
|
* @brief Does this request change group membership.
|
|
*
|
|
* Does the request change the ses group memberships, i.e. does it contain the
|
|
* member attribute.
|
|
*
|
|
* @param req the request to examine.
|
|
*
|
|
* @return True if the request modifies the users group memberships.
|
|
*/
|
|
static bool has_group_membership_changes(struct ldb_request *req)
|
|
{
|
|
struct ldb_message_element *el = NULL;
|
|
const struct ldb_message *msg = NULL;
|
|
|
|
msg = dsdb_audit_get_message(req);
|
|
el = ldb_msg_find_element(msg, "member");
|
|
|
|
return (el != NULL);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* @brief Install the callback function to log an add request.
|
|
*
|
|
* Install the callback function to log an add request changing the users
|
|
* group memberships. As we want to log the returned status code, we need to
|
|
* register a callback function that will be called once the operation has
|
|
* completed.
|
|
*
|
|
* This function reads the current user record so that we can log the before
|
|
* and after state.
|
|
*
|
|
* @param module The ldb module.
|
|
* @param req The modify request.
|
|
*
|
|
* @return and LDB status code.
|
|
*/
|
|
static int set_group_membership_add_callback(
|
|
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;
|
|
/*
|
|
* Adding group memberships so will need to log the changes.
|
|
*/
|
|
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;
|
|
context->log_changes = log_group_membership_changes;
|
|
/*
|
|
* 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,
|
|
group_audit_callback,
|
|
req);
|
|
if (ret != LDB_SUCCESS) {
|
|
return ret;
|
|
}
|
|
return ldb_next_request(module, new_req);
|
|
}
|
|
|
|
|
|
/*
|
|
* @brief Install the callback function to log a modify request.
|
|
*
|
|
* Install the callback function to log a modify request changing the primary
|
|
* group . As we want to log the returned status code, we need to register a
|
|
* callback function that will be called once the operation has completed.
|
|
*
|
|
* This function reads the current user record so that we can log the before
|
|
* and after state.
|
|
*
|
|
* @param module The ldb module.
|
|
* @param req The modify request.
|
|
*
|
|
* @return and LDB status code.
|
|
*/
|
|
static int set_primary_group_modify_callback(
|
|
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;
|
|
const struct ldb_message *msg = NULL;
|
|
struct ldb_result *res = NULL;
|
|
int ret;
|
|
|
|
TALLOC_CTX *ctx = talloc_new(NULL);
|
|
|
|
ldb = ldb_module_get_ctx(module);
|
|
|
|
context = talloc_zero(req, struct audit_callback_context);
|
|
if (context == NULL) {
|
|
ret = ldb_oom(ldb);
|
|
goto exit;
|
|
}
|
|
context->request = req;
|
|
context->module = module;
|
|
context->log_changes = log_user_primary_group_change;
|
|
|
|
msg = dsdb_audit_get_message(req);
|
|
ret = dsdb_module_search_dn(
|
|
module,
|
|
ctx,
|
|
&res,
|
|
msg->dn,
|
|
primary_group_attr,
|
|
DSDB_FLAG_NEXT_MODULE |
|
|
DSDB_SEARCH_REVEAL_INTERNALS |
|
|
DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
|
|
NULL);
|
|
if (ret == LDB_SUCCESS) {
|
|
uint32_t pg;
|
|
pg = ldb_msg_find_attr_as_uint(
|
|
res->msgs[0],
|
|
"primaryGroupID",
|
|
~0);
|
|
context->primary_group = pg;
|
|
}
|
|
/*
|
|
* 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.add.message,
|
|
req->controls,
|
|
context,
|
|
group_audit_callback,
|
|
req);
|
|
if (ret != LDB_SUCCESS) {
|
|
goto exit;
|
|
}
|
|
ret = ldb_next_request(module, new_req);
|
|
exit:
|
|
TALLOC_FREE(ctx);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* @brief Install the callback function to log an add request.
|
|
*
|
|
* Install the callback function to log an add request changing the primary
|
|
* group . As we want to log the returned status code, we need to register a
|
|
* callback function that will be called once the operation has completed.
|
|
*
|
|
* This function reads the current user record so that we can log the before
|
|
* and after state.
|
|
*
|
|
* @param module The ldb module.
|
|
* @param req The modify request.
|
|
*
|
|
* @return and LDB status code.
|
|
*/
|
|
static int set_primary_group_add_callback(
|
|
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;
|
|
/*
|
|
* Adding a user with a primary group.
|
|
*/
|
|
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;
|
|
context->log_changes = log_user_primary_group_change;
|
|
/*
|
|
* 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,
|
|
group_audit_callback,
|
|
req);
|
|
if (ret != LDB_SUCCESS) {
|
|
return ret;
|
|
}
|
|
return ldb_next_request(module, new_req);
|
|
}
|
|
|
|
/*
|
|
* @brief Module handler for add operations.
|
|
*
|
|
* Inspect the current add request, and if needed log any group membership
|
|
* changes.
|
|
*
|
|
* @param module The ldb module.
|
|
* @param req The modify request.
|
|
*
|
|
* @return and LDB status code.
|
|
*/
|
|
static int group_add(
|
|
struct ldb_module *module,
|
|
struct ldb_request *req)
|
|
{
|
|
|
|
struct audit_context *ac =
|
|
talloc_get_type(
|
|
ldb_module_get_private(module),
|
|
struct audit_context);
|
|
/*
|
|
* Currently we don't log replicated group changes
|
|
*/
|
|
if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
|
|
return ldb_next_request(module, req);
|
|
}
|
|
|
|
if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL) ||
|
|
CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
|
|
(ac->msg_ctx && ac->send_events)) {
|
|
/*
|
|
* Avoid the overheads of logging unless it has been
|
|
* enabled
|
|
*/
|
|
if (has_group_membership_changes(req)) {
|
|
return set_group_membership_add_callback(module, req);
|
|
}
|
|
if (has_primary_group_id(req)) {
|
|
return set_primary_group_add_callback(module, req);
|
|
}
|
|
}
|
|
return ldb_next_request(module, req);
|
|
}
|
|
|
|
/*
|
|
* @brief Module handler for delete operations.
|
|
*
|
|
* Currently there is no logging for delete operations.
|
|
*
|
|
* @param module The ldb module.
|
|
* @param req The modify request.
|
|
*
|
|
* @return and LDB status code.
|
|
*/
|
|
static int group_delete(
|
|
struct ldb_module *module,
|
|
struct ldb_request *req)
|
|
{
|
|
return ldb_next_request(module, req);
|
|
}
|
|
|
|
/*
|
|
* @brief Install the callback function to log a modify request.
|
|
*
|
|
* Install the callback function to log a modify request. As we want to log the
|
|
* returned status code, we need to register a callback function that will be
|
|
* called once the operation has completed.
|
|
*
|
|
* This function reads the current user record so that we can log the before
|
|
* and after state.
|
|
*
|
|
* @param module The ldb module.
|
|
* @param req The modify request.
|
|
*
|
|
* @return and LDB status code.
|
|
*/
|
|
static int set_group_modify_callback(
|
|
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;
|
|
struct ldb_result *res = 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;
|
|
context->log_changes = log_group_membership_changes;
|
|
|
|
/*
|
|
* About to change the group memberships need to read
|
|
* the current state from the database.
|
|
*/
|
|
ret = dsdb_module_search_dn(
|
|
module,
|
|
context,
|
|
&res,
|
|
req->op.add.message->dn,
|
|
group_attrs,
|
|
DSDB_FLAG_NEXT_MODULE |
|
|
DSDB_SEARCH_REVEAL_INTERNALS |
|
|
DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
|
|
NULL);
|
|
if (ret == LDB_SUCCESS) {
|
|
context->members = ldb_msg_find_element(res->msgs[0], "member");
|
|
}
|
|
|
|
ret = ldb_build_mod_req(
|
|
&new_req,
|
|
ldb,
|
|
req,
|
|
req->op.mod.message,
|
|
req->controls,
|
|
context,
|
|
group_audit_callback,
|
|
req);
|
|
if (ret != LDB_SUCCESS) {
|
|
return ret;
|
|
}
|
|
return ldb_next_request(module, new_req);
|
|
}
|
|
|
|
/*
|
|
* @brief Module handler for modify operations.
|
|
*
|
|
* Inspect the current modify request, and if needed log any group membership
|
|
* changes.
|
|
*
|
|
* @param module The ldb module.
|
|
* @param req The modify request.
|
|
*
|
|
* @return and LDB status code.
|
|
*/
|
|
static int group_modify(
|
|
struct ldb_module *module,
|
|
struct ldb_request *req)
|
|
{
|
|
|
|
struct audit_context *ac =
|
|
talloc_get_type(
|
|
ldb_module_get_private(module),
|
|
struct audit_context);
|
|
/*
|
|
* Currently we don't log replicated group changes
|
|
*/
|
|
if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
|
|
return ldb_next_request(module, req);
|
|
}
|
|
|
|
if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL) ||
|
|
CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
|
|
(ac->msg_ctx && ac->send_events)) {
|
|
/*
|
|
* Avoid the overheads of logging unless it has been
|
|
* enabled
|
|
*/
|
|
if (has_group_membership_changes(req)) {
|
|
return set_group_modify_callback(module, req);
|
|
}
|
|
if (has_primary_group_id(req)) {
|
|
return set_primary_group_modify_callback(module, req);
|
|
}
|
|
}
|
|
return ldb_next_request(module, req);
|
|
}
|
|
|
|
/*
|
|
* @brief ldb module initialisation
|
|
*
|
|
* Initialise the module, loading the private data etc.
|
|
*
|
|
* @param module The ldb module to initialise.
|
|
*
|
|
* @return An LDB status code.
|
|
*/
|
|
static int group_init(struct ldb_module *module)
|
|
{
|
|
|
|
struct ldb_context *ldb = ldb_module_get_ctx(module);
|
|
struct audit_context *context = 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);
|
|
|
|
context = talloc_zero(module, struct audit_context);
|
|
if (context == NULL) {
|
|
return ldb_module_oom(module);
|
|
}
|
|
|
|
if (lp_ctx && lpcfg_dsdb_group_change_notification(lp_ctx)) {
|
|
context->send_events = true;
|
|
context->msg_ctx = imessaging_client_init(context,
|
|
lp_ctx,
|
|
ev);
|
|
}
|
|
|
|
ldb_module_set_private(module, context);
|
|
return ldb_next_init(module);
|
|
}
|
|
|
|
static const struct ldb_module_ops ldb_group_audit_log_module_ops = {
|
|
.name = "group_audit_log",
|
|
.add = group_add,
|
|
.modify = group_modify,
|
|
.del = group_delete,
|
|
.init_context = group_init,
|
|
};
|
|
|
|
int ldb_group_audit_log_module_init(const char *version)
|
|
{
|
|
LDB_MODULE_CHECK_VERSION(version);
|
|
return ldb_register_module(&ldb_group_audit_log_module_ops);
|
|
}
|