4 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 * Provide an audit log of changes made to group memberships
26 #include "ldb_module.h"
27 #include "lib/audit_logging/audit_logging.h"
28 #include "librpc/gen_ndr/windows_event_ids.h"
30 #include "dsdb/samdb/samdb.h"
31 #include "dsdb/samdb/ldb_modules/util.h"
32 #include "dsdb/samdb/ldb_modules/audit_util_proto.h"
33 #include "libcli/security/dom_sid.h"
34 #include "auth/common_auth.h"
35 #include "param/param.h"
37 #define AUDIT_JSON_TYPE "groupChange"
38 #define AUDIT_HR_TAG "Group Change"
41 #define GROUP_LOG_LVL 5
43 static const char *const group_attrs[] = {"member", "groupType", NULL};
44 static const char *const group_type_attr[] = {"groupType", NULL};
45 static const char * const primary_group_attr[] = {
50 struct audit_context {
52 struct imessaging_context *msg_ctx;
55 struct audit_callback_context {
56 struct ldb_request *request;
57 struct ldb_module *module;
58 struct ldb_message_element *members;
59 uint32_t primary_group;
61 struct audit_callback_context *acc,
66 * @brief get the transaction id.
68 * Get the id of the transaction that the current request is contained in.
70 * @param req the request.
72 * @return the transaction id GUID, or NULL if it is not there.
74 static struct GUID *get_transaction_id(
75 const struct ldb_request *request)
77 struct ldb_control *control;
78 struct dsdb_control_transaction_identifier *transaction_id;
80 control = ldb_request_get_control(
81 discard_const(request),
82 DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID);
83 if (control == NULL) {
86 transaction_id = talloc_get_type(
88 struct dsdb_control_transaction_identifier);
89 if (transaction_id == NULL) {
92 return &transaction_id->transaction_guid;
96 * @brief generate a JSON log entry for a group change.
98 * Generate a JSON object containing details of a users group change.
100 * @param module the ldb module
101 * @param request the ldb_request
102 * @param action the change action being performed
103 * @param user the user name
104 * @param group the group name
105 * @param status the ldb status code for the ldb operation.
107 * @return A json object containing the details.
108 * NULL if an error was detected
110 static struct json_object audit_group_json(const struct ldb_module *module,
111 const struct ldb_request *request,
115 const enum event_id_type event_id,
118 struct ldb_context *ldb = NULL;
119 const struct dom_sid *sid = NULL;
120 struct json_object wrapper = json_empty_object;
121 struct json_object audit = json_empty_object;
122 const struct tsocket_address *remote = NULL;
123 const struct GUID *unique_session_token = NULL;
124 struct GUID *transaction_id = NULL;
127 ldb = ldb_module_get_ctx(discard_const(module));
129 remote = dsdb_audit_get_remote_address(ldb);
130 sid = dsdb_audit_get_user_sid(module);
131 unique_session_token = dsdb_audit_get_unique_session_token(module);
132 transaction_id = get_transaction_id(request);
134 audit = json_new_object();
135 if (json_is_invalid(&audit)) {
138 rc = json_add_version(&audit, AUDIT_MAJOR, AUDIT_MINOR);
142 if (event_id != EVT_ID_NONE) {
143 rc = json_add_int(&audit, "eventId", event_id);
148 rc = json_add_int(&audit, "statusCode", status);
152 rc = json_add_string(&audit, "status", ldb_strerror(status));
156 rc = json_add_string(&audit, "action", action);
160 rc = json_add_address(&audit, "remoteAddress", remote);
164 rc = json_add_sid(&audit, "userSid", sid);
168 rc = json_add_string(&audit, "group", group);
172 rc = json_add_guid(&audit, "transactionId", transaction_id);
176 rc = json_add_guid(&audit, "sessionId", unique_session_token);
180 rc = json_add_string(&audit, "user", user);
185 wrapper = json_new_object();
186 if (json_is_invalid(&wrapper)) {
189 rc = json_add_timestamp(&wrapper);
193 rc = json_add_string(&wrapper, "type", AUDIT_JSON_TYPE);
197 rc = json_add_object(&wrapper, AUDIT_JSON_TYPE, &audit);
205 * On a failure audit will not have been added to wrapper so it
206 * needs to free it to avoid a leak.
208 * wrapper is freed to invalidate it as it will have only been
209 * partially constructed and may be inconsistent.
211 * All the json manipulation routines handle a freed object correctly
215 DBG_ERR("Failed to create group change JSON log message\n");
220 * @brief generate a human readable log entry for a group change.
222 * Generate a human readable log entry containing details of a users group
225 * @param ctx the talloc context owning the returned log entry
226 * @param module the ldb module
227 * @param request the ldb_request
228 * @param action the change action being performed
229 * @param user the user name
230 * @param group the group name
231 * @param status the ldb status code for the ldb operation.
233 * @return A human readable log line.
235 static char *audit_group_human_readable(
237 const struct ldb_module *module,
238 const struct ldb_request *request,
244 struct ldb_context *ldb = NULL;
245 const char *remote_host = NULL;
246 const struct dom_sid *sid = NULL;
247 const char *user_sid = NULL;
248 const char *timestamp = NULL;
249 char *log_entry = NULL;
251 TALLOC_CTX *ctx = talloc_new(NULL);
253 ldb = ldb_module_get_ctx(discard_const(module));
255 remote_host = dsdb_audit_get_remote_host(ldb, ctx);
256 sid = dsdb_audit_get_user_sid(module);
257 user_sid = dom_sid_string(ctx, sid);
258 timestamp = audit_get_timestamp(ctx);
260 log_entry = talloc_asprintf(
262 "[%s] at [%s] status [%s] "
263 "Remote host [%s] SID [%s] Group [%s] User [%s]",
266 ldb_strerror(status),
276 * @brief generate an array of parsed_dns, deferring the actual parsing.
278 * Get an array of 'struct parsed_dns' without the parsing.
279 * The parsed_dns are parsed only when needed to avoid the expense of parsing.
281 * This procedure assumes that the dn's are sorted in GUID order and contains
282 * no duplicates. This should be valid as the module sits below repl_meta_data
283 * which ensures this.
285 * @param mem_ctx The memory context that will own the generated array
286 * @param el The message element used to generate the array.
288 * @return an array of struct parsed_dns, or NULL in the event of an error
290 static struct parsed_dn *get_parsed_dns(
292 struct ldb_message_element *el)
295 struct parsed_dn *pdn = NULL;
297 if (el == NULL || el->num_values == 0) {
301 ret = get_parsed_dns_trusted(mem_ctx, el, &pdn);
302 if (ret == LDB_ERR_OPERATIONS_ERROR) {
303 DBG_ERR("Out of memory\n");
310 enum dn_compare_result {
317 * @brief compare parsed_dn, using GUID ordering
319 * Compare two parsed_dn structures, using GUID ordering.
320 * To avoid the overhead of parsing the DN's this function does a binary
321 * compare first. The DN's tre only parsed if they are not equal at a binary
324 * @param ctx talloc context that will own the parsed dsdb_dn
325 * @param ldb ldb_context
326 * @param dn1 The first dn
327 * @param dn2 The second dn
329 * @return BINARY_EQUAL values are equal at a binary level
330 * EQUAL DN's are equal but the meta data is different
331 * LESS_THAN dn1's GUID is less than dn2's GUID
332 * GREATER_THAN dn1's GUID is greater than dn2's GUID
335 static enum dn_compare_result dn_compare(
337 struct ldb_context *ldb,
338 struct parsed_dn *dn1,
339 struct parsed_dn *dn2) {
344 * Do a binary compare first to avoid unnecessary parsing
346 if (data_blob_cmp(dn1->v, dn2->v) == 0) {
348 * Values are equal at a binary level so no need
349 * for further processing
354 * Values not equal at the binary level, so lets
355 * do a GUID ordering compare. To do this we will need to ensure
356 * that the dn's have been parsed.
358 if (dn1->dsdb_dn == NULL) {
359 really_parse_trusted_dn(
365 if (dn2->dsdb_dn == NULL) {
366 really_parse_trusted_dn(
373 res = ndr_guid_compare(&dn1->guid, &dn2->guid);
376 } else if (res == 0) {
384 * @brief Get the DN of a users primary group as a printable string.
386 * Get the DN of a users primary group as a printable string.
388 * @param mem_ctx Talloc context the the returned string will be allocated on.
389 * @param module The ldb module
390 * @param account_sid The SID for the uses account.
391 * @param primary_group_rid The RID for the users primary group.
393 * @return a formatted DN, or null if there is an error.
395 static const char *get_primary_group_dn(
397 struct ldb_module *module,
398 struct dom_sid *account_sid,
399 uint32_t primary_group_rid)
403 struct ldb_context *ldb = NULL;
404 struct dom_sid *domain_sid = NULL;
405 struct dom_sid *primary_group_sid = NULL;
407 struct ldb_dn *dn = NULL;
408 struct ldb_message *msg = NULL;
411 ldb = ldb_module_get_ctx(module);
413 status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
414 if (!NT_STATUS_IS_OK(status)) {
418 primary_group_sid = dom_sid_add_rid(
422 if (!primary_group_sid) {
426 sid = dom_sid_string(mem_ctx, primary_group_sid);
431 dn = ldb_dn_new_fmt(mem_ctx, ldb, "<SID=%s>", sid);
435 rc = dsdb_search_one(
444 if (rc != LDB_SUCCESS) {
448 return ldb_dn_get_linearized(msg->dn);
452 * @brief Log details of a change to a users primary group.
454 * Log details of a change to a users primary group.
455 * There is no windows event id associated with a Primary Group change.
456 * However for a new user we generate an added to group event.
458 * @param module The ldb module.
459 * @param request The request being logged.
460 * @param action Description of the action being performed.
461 * @param group The linearized for of the group DN
462 * @param status the LDB status code for the processing of the request.
465 static void log_primary_group_change(
466 struct ldb_module *module,
467 const struct ldb_request *request,
472 const char *user = NULL;
474 struct audit_context *ac =
476 ldb_module_get_private(module),
477 struct audit_context);
479 TALLOC_CTX *ctx = talloc_new(NULL);
481 user = dsdb_audit_get_primary_dn(request);
482 if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL)) {
483 char *message = NULL;
484 message = audit_group_human_readable(
492 audit_log_human_text(
495 DBGC_DSDB_GROUP_AUDIT,
497 TALLOC_FREE(message);
500 if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
501 (ac->msg_ctx && ac->send_events)) {
503 struct json_object json;
504 json = audit_group_json(
505 module, request, action, user, group, EVT_ID_NONE, status);
508 DBGC_DSDB_GROUP_AUDIT_JSON,
510 if (ac->send_events) {
513 DSDB_GROUP_EVENT_NAME,
518 if (request->operation == LDB_ADD) {
520 * Have just added a user, generate a groupChange
521 * message indicating the user has been added to thier
530 * @brief Log details of a single change to a users group membership.
532 * Log details of a change to a users group membership, except for changes
533 * to their primary group which is handled by log_primary_group_change.
535 * @param module The ldb module.
536 * @param request The request being logged.
537 * @param action Description of the action being performed.
538 * @param user The linearized form of the users DN
539 * @param status the LDB status code for the processing of the request.
542 static void log_membership_change(struct ldb_module *module,
543 const struct ldb_request *request,
546 const enum event_id_type event_id,
549 const char *group = NULL;
550 struct audit_context *ac =
552 ldb_module_get_private(module),
553 struct audit_context);
555 TALLOC_CTX *ctx = talloc_new(NULL);
556 group = dsdb_audit_get_primary_dn(request);
557 if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL)) {
558 char *message = NULL;
559 message = audit_group_human_readable(
567 audit_log_human_text(
570 DBGC_DSDB_GROUP_AUDIT,
572 TALLOC_FREE(message);
575 if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
576 (ac->msg_ctx && ac->send_events)) {
577 struct json_object json;
578 json = audit_group_json(
579 module, request, action, user, group, event_id, status);
582 DBGC_DSDB_GROUP_AUDIT_JSON,
584 if (ac->send_events) {
587 DSDB_GROUP_EVENT_NAME,
597 * @brief Get the windows event type id for removing a user from a group type.
599 * @param group_type the type of the current group, see libds/common/flags.h
601 * @return the Windows Event Id
604 static enum event_id_type get_remove_member_event(uint32_t group_type)
607 switch (group_type) {
608 case GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
609 return EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP;
610 case GTYPE_SECURITY_GLOBAL_GROUP:
611 return EVT_ID_USER_REMOVED_FROM_GLOBAL_SEC_GROUP;
612 case GTYPE_SECURITY_DOMAIN_LOCAL_GROUP:
613 return EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP;
614 case GTYPE_SECURITY_UNIVERSAL_GROUP:
615 return EVT_ID_USER_REMOVED_FROM_UNIVERSAL_SEC_GROUP;
616 case GTYPE_DISTRIBUTION_GLOBAL_GROUP:
617 return EVT_ID_USER_REMOVED_FROM_GLOBAL_GROUP;
618 case GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP:
619 return EVT_ID_USER_REMOVED_FROM_LOCAL_GROUP;
620 case GTYPE_DISTRIBUTION_UNIVERSAL_GROUP:
621 return EVT_ID_USER_REMOVED_FROM_UNIVERSAL_GROUP;
628 * @brief Get the windows event type id for adding a user to a group type.
630 * @param group_type the type of the current group, see libds/common/flags.h
632 * @return the Windows Event Id
635 static enum event_id_type get_add_member_event(uint32_t group_type)
638 switch (group_type) {
639 case GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
640 return EVT_ID_USER_ADDED_TO_LOCAL_SEC_GROUP;
641 case GTYPE_SECURITY_GLOBAL_GROUP:
642 return EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP;
643 case GTYPE_SECURITY_DOMAIN_LOCAL_GROUP:
644 return EVT_ID_USER_ADDED_TO_LOCAL_SEC_GROUP;
645 case GTYPE_SECURITY_UNIVERSAL_GROUP:
646 return EVT_ID_USER_ADDED_TO_UNIVERSAL_SEC_GROUP;
647 case GTYPE_DISTRIBUTION_GLOBAL_GROUP:
648 return EVT_ID_USER_ADDED_TO_GLOBAL_GROUP;
649 case GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP:
650 return EVT_ID_USER_ADDED_TO_LOCAL_GROUP;
651 case GTYPE_DISTRIBUTION_UNIVERSAL_GROUP:
652 return EVT_ID_USER_ADDED_TO_UNIVERSAL_GROUP;
659 * @brief Log all the changes to a users group membership.
661 * Log details of a change to a users group memberships, except for changes
662 * to their primary group which is handled by log_primary_group_change.
664 * @param module The ldb module.
665 * @param request The request being logged.
666 * @param action Description of the action being performed.
667 * @param user The linearized form of the users DN
668 * @param status the LDB status code for the processing of the request.
671 static void log_membership_changes(struct ldb_module *module,
672 const struct ldb_request *request,
673 struct ldb_message_element *el,
674 struct ldb_message_element *old_el,
678 unsigned int i, old_i, new_i;
679 unsigned int old_num_values;
680 unsigned int max_num_values;
681 unsigned int new_num_values;
682 struct parsed_dn *old_val = NULL;
683 struct parsed_dn *new_val = NULL;
684 struct parsed_dn *new_values = NULL;
685 struct parsed_dn *old_values = NULL;
686 struct ldb_context *ldb = NULL;
688 TALLOC_CTX *ctx = talloc_new(NULL);
690 old_num_values = old_el ? old_el->num_values : 0;
691 new_num_values = el ? el->num_values : 0;
692 max_num_values = old_num_values + new_num_values;
694 if (max_num_values == 0) {
696 * There is nothing to do!
702 old_values = get_parsed_dns(ctx, old_el);
703 new_values = get_parsed_dns(ctx, el);
704 ldb = ldb_module_get_ctx(module);
708 for (i = 0; i < max_num_values; i++) {
709 enum dn_compare_result cmp;
710 if (old_i < old_num_values && new_i < new_num_values) {
712 * Both list have values, so compare the values
714 old_val = &old_values[old_i];
715 new_val = &new_values[new_i];
716 cmp = dn_compare(ctx, ldb, old_val, new_val);
717 } else if (old_i < old_num_values) {
719 * the new list is empty, read the old list
721 old_val = &old_values[old_i];
724 } else if (new_i < new_num_values) {
726 * the old list is empty, read new list
729 new_val = &new_values[new_i];
735 if (cmp == LESS_THAN) {
737 * Have an entry in the original record that is not in
738 * the new record. So it's been deleted
740 const char *user = NULL;
741 enum event_id_type event_id;
742 if (old_val->dsdb_dn == NULL) {
743 really_parse_trusted_dn(
749 user = ldb_dn_get_linearized(old_val->dsdb_dn->dn);
750 event_id = get_remove_member_event(group_type);
751 log_membership_change(
752 module, request, "Removed", user, event_id, status);
754 } else if (cmp == BINARY_EQUAL) {
756 * DN's unchanged at binary level so nothing to do.
760 } else if (cmp == EQUAL) {
762 * DN is unchanged now need to check the flags to
763 * determine if a record has been deleted or undeleted
767 if (old_val->dsdb_dn == NULL) {
768 really_parse_trusted_dn(
774 if (new_val->dsdb_dn == NULL) {
775 really_parse_trusted_dn(
782 dsdb_get_extended_dn_uint32(
783 old_val->dsdb_dn->dn,
786 dsdb_get_extended_dn_uint32(
787 new_val->dsdb_dn->dn,
790 if (new_flags == old_flags) {
792 * No changes to the Repl meta data so can
793 * no need to log the change
799 if (new_flags & DSDB_RMD_FLAG_DELETED) {
801 * DN has been deleted.
803 const char *user = NULL;
804 enum event_id_type event_id;
805 user = ldb_dn_get_linearized(
806 old_val->dsdb_dn->dn);
807 event_id = get_remove_member_event(group_type);
808 log_membership_change(module,
816 * DN has been re-added
818 const char *user = NULL;
819 enum event_id_type event_id;
820 user = ldb_dn_get_linearized(
821 new_val->dsdb_dn->dn);
822 event_id = get_add_member_event(group_type);
823 log_membership_change(module,
834 * Member in the updated record that's not in the
835 * original, so it must have been added.
837 const char *user = NULL;
838 enum event_id_type event_id;
839 if ( new_val->dsdb_dn == NULL) {
840 really_parse_trusted_dn(
846 user = ldb_dn_get_linearized(new_val->dsdb_dn->dn);
847 event_id = get_add_member_event(group_type);
848 log_membership_change(
849 module, request, "Added", user, event_id, status);
858 * @brief log a group change message for a newly added user.
860 * When a user is added we need to generate a GroupChange Add message to
861 * log that the user has been added to their PrimaryGroup
863 static void log_new_user_added_to_primary_group(
865 struct audit_callback_context *acc,
870 enum event_id_type event_id = EVT_ID_NONE;
871 struct ldb_result *res = NULL;
872 struct ldb_dn *group_dn = NULL;
873 struct ldb_context *ldb = NULL;
876 ldb = ldb_module_get_ctx(acc->module);
877 group_dn = ldb_dn_new(ctx, ldb, group);
878 ret = dsdb_module_search_dn(acc->module,
883 DSDB_FLAG_NEXT_MODULE |
884 DSDB_SEARCH_REVEAL_INTERNALS |
885 DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
887 if (ret == LDB_SUCCESS) {
888 const char *user = NULL;
890 ldb_msg_find_attr_as_uint(res->msgs[0], "groupType", 0);
891 event_id = get_add_member_event(group_type);
892 user = dsdb_audit_get_primary_dn(acc->request);
893 log_membership_change(
894 acc->module, acc->request, "Added", user, event_id, status);
899 * @brief Log the details of a primary group change.
901 * Retrieve the users primary groupo after the operation has completed
902 * and call log_primary_group_change to log the actual changes.
904 * @param acc details of the primary group before the operation.
905 * @param status The status code returned by the operation.
907 * @return an LDB status code.
909 static void log_user_primary_group_change(
910 struct audit_callback_context *acc,
913 TALLOC_CTX *ctx = talloc_new(NULL);
915 struct dom_sid *account_sid = NULL;
917 const struct ldb_message *msg = dsdb_audit_get_message(acc->request);
919 if (status == LDB_SUCCESS && msg != NULL) {
920 struct ldb_result *res = NULL;
921 ret = dsdb_module_search_dn(
927 DSDB_FLAG_NEXT_MODULE |
928 DSDB_SEARCH_REVEAL_INTERNALS |
929 DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
931 if (ret == LDB_SUCCESS) {
932 new_rid = ldb_msg_find_attr_as_uint(
936 account_sid = samdb_result_dom_sid(
943 * If we don't have a new value then the user has been deleted
944 * which we currently do not log.
945 * Otherwise only log if the primary group has actually changed.
947 if (account_sid != NULL &&
949 acc->primary_group != new_rid) {
950 const char* group = get_primary_group_dn(
955 log_primary_group_change(
962 * Are we adding a new user with the primaryGroupID
963 * set. If so and we're generating JSON audit logs, will need to
964 * generate an "Add" message with the appropriate windows
967 if (acc->request->operation == LDB_ADD) {
968 log_new_user_added_to_primary_group(
969 ctx, acc, group, status);
976 * @brief log the changes to users group membership.
978 * Retrieve the users group memberships after the operation has completed
979 * and call log_membership_changes to log the actual changes.
981 * @param acc details of the group memberships before the operation.
982 * @param status The status code returned by the operation.
985 static void log_group_membership_changes(
986 struct audit_callback_context *acc,
989 TALLOC_CTX *ctx = talloc_new(NULL);
990 struct ldb_message_element *new_val = NULL;
992 uint32_t group_type = 0;
993 const struct ldb_message *msg = dsdb_audit_get_message(acc->request);
994 if (status == LDB_SUCCESS && msg != NULL) {
995 struct ldb_result *res = NULL;
996 ret = dsdb_module_search_dn(
1002 DSDB_FLAG_NEXT_MODULE |
1003 DSDB_SEARCH_REVEAL_INTERNALS |
1004 DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
1006 if (ret == LDB_SUCCESS) {
1007 new_val = ldb_msg_find_element(res->msgs[0], "member");
1008 group_type = ldb_msg_find_attr_as_uint(
1009 res->msgs[0], "groupType", 0);
1010 log_membership_changes(acc->module,
1021 * If we get here either
1022 * one of the lower level modules failed and the group record did
1025 * the updated group record could not be read.
1027 * In both cases it does not make sense to log individual membership
1028 * changes so we log a group membership change "Failure" message.
1031 log_membership_change(acc->module,
1041 * @brief call back function to log changes to the group memberships.
1043 * Call back function to log changes to the uses broup memberships.
1045 * @param req the ldb request.
1046 * @param ares the ldb result
1048 * @return am LDB status code.
1050 static int group_audit_callback(
1051 struct ldb_request *req,
1052 struct ldb_reply *ares)
1054 struct audit_callback_context *ac = NULL;
1056 ac = talloc_get_type(
1058 struct audit_callback_context);
1061 return ldb_module_done(
1062 ac->request, NULL, NULL,
1063 LDB_ERR_OPERATIONS_ERROR);
1066 /* pass on to the callback */
1067 switch (ares->type) {
1068 case LDB_REPLY_ENTRY:
1069 return ldb_module_send_entry(
1074 case LDB_REPLY_REFERRAL:
1075 return ldb_module_send_referral(
1079 case LDB_REPLY_DONE:
1081 * Log on DONE now we have a result code
1083 ac->log_changes(ac, ares->error);
1084 return ldb_module_done(
1093 return LDB_ERR_OPERATIONS_ERROR;
1098 * @brief Does this request change the primary group.
1100 * Does the request change the primary group, i.e. does it contain the
1101 * primaryGroupID attribute.
1103 * @param req the request to examine.
1105 * @return True if the request modifies the primary group.
1107 static bool has_primary_group_id(struct ldb_request *req)
1109 struct ldb_message_element *el = NULL;
1110 const struct ldb_message *msg = NULL;
1112 msg = dsdb_audit_get_message(req);
1113 el = ldb_msg_find_element(msg, "primaryGroupID");
1115 return (el != NULL);
1119 * @brief Does this request change group membership.
1121 * Does the request change the ses group memberships, i.e. does it contain the
1124 * @param req the request to examine.
1126 * @return True if the request modifies the users group memberships.
1128 static bool has_group_membership_changes(struct ldb_request *req)
1130 struct ldb_message_element *el = NULL;
1131 const struct ldb_message *msg = NULL;
1133 msg = dsdb_audit_get_message(req);
1134 el = ldb_msg_find_element(msg, "member");
1136 return (el != NULL);
1142 * @brief Install the callback function to log an add request.
1144 * Install the callback function to log an add request changing the users
1145 * group memberships. As we want to log the returned status code, we need to
1146 * register a callback function that will be called once the operation has
1149 * This function reads the current user record so that we can log the before
1152 * @param module The ldb module.
1153 * @param req The modify request.
1155 * @return and LDB status code.
1157 static int set_group_membership_add_callback(
1158 struct ldb_module *module,
1159 struct ldb_request *req)
1161 struct audit_callback_context *context = NULL;
1162 struct ldb_request *new_req = NULL;
1163 struct ldb_context *ldb = NULL;
1166 * Adding group memberships so will need to log the changes.
1168 ldb = ldb_module_get_ctx(module);
1169 context = talloc_zero(req, struct audit_callback_context);
1171 if (context == NULL) {
1172 return ldb_oom(ldb);
1174 context->request = req;
1175 context->module = module;
1176 context->log_changes = log_group_membership_changes;
1178 * We want to log the return code status, so we need to register
1179 * a callback function to get the actual result.
1180 * We need to take a new copy so that we don't alter the callers copy
1182 ret = ldb_build_add_req(
1186 req->op.add.message,
1189 group_audit_callback,
1191 if (ret != LDB_SUCCESS) {
1194 return ldb_next_request(module, new_req);
1199 * @brief Install the callback function to log a modify request.
1201 * Install the callback function to log a modify request changing the primary
1202 * group . As we want to log the returned status code, we need to register a
1203 * callback function that will be called once the operation has completed.
1205 * This function reads the current user record so that we can log the before
1208 * @param module The ldb module.
1209 * @param req The modify request.
1211 * @return and LDB status code.
1213 static int set_primary_group_modify_callback(
1214 struct ldb_module *module,
1215 struct ldb_request *req)
1217 struct audit_callback_context *context = NULL;
1218 struct ldb_request *new_req = NULL;
1219 struct ldb_context *ldb = NULL;
1220 const struct ldb_message *msg = NULL;
1221 struct ldb_result *res = NULL;
1224 TALLOC_CTX *ctx = talloc_new(NULL);
1226 ldb = ldb_module_get_ctx(module);
1228 context = talloc_zero(req, struct audit_callback_context);
1229 if (context == NULL) {
1233 context->request = req;
1234 context->module = module;
1235 context->log_changes = log_user_primary_group_change;
1237 msg = dsdb_audit_get_message(req);
1238 ret = dsdb_module_search_dn(
1244 DSDB_FLAG_NEXT_MODULE |
1245 DSDB_SEARCH_REVEAL_INTERNALS |
1246 DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
1248 if (ret == LDB_SUCCESS) {
1250 pg = ldb_msg_find_attr_as_uint(
1254 context->primary_group = pg;
1257 * We want to log the return code status, so we need to register
1258 * a callback function to get the actual result.
1259 * We need to take a new copy so that we don't alter the callers copy
1261 ret = ldb_build_mod_req(
1265 req->op.add.message,
1268 group_audit_callback,
1270 if (ret != LDB_SUCCESS) {
1273 ret = ldb_next_request(module, new_req);
1280 * @brief Install the callback function to log an add request.
1282 * Install the callback function to log an add request changing the primary
1283 * group . As we want to log the returned status code, we need to register a
1284 * callback function that will be called once the operation has completed.
1286 * This function reads the current user record so that we can log the before
1289 * @param module The ldb module.
1290 * @param req The modify request.
1292 * @return and LDB status code.
1294 static int set_primary_group_add_callback(
1295 struct ldb_module *module,
1296 struct ldb_request *req)
1298 struct audit_callback_context *context = NULL;
1299 struct ldb_request *new_req = NULL;
1300 struct ldb_context *ldb = NULL;
1303 * Adding a user with a primary group.
1305 ldb = ldb_module_get_ctx(module);
1306 context = talloc_zero(req, struct audit_callback_context);
1308 if (context == NULL) {
1309 return ldb_oom(ldb);
1311 context->request = req;
1312 context->module = module;
1313 context->log_changes = log_user_primary_group_change;
1315 * We want to log the return code status, so we need to register
1316 * a callback function to get the actual result.
1317 * We need to take a new copy so that we don't alter the callers copy
1319 ret = ldb_build_add_req(
1323 req->op.add.message,
1326 group_audit_callback,
1328 if (ret != LDB_SUCCESS) {
1331 return ldb_next_request(module, new_req);
1335 * @brief Module handler for add operations.
1337 * Inspect the current add request, and if needed log any group membership
1340 * @param module The ldb module.
1341 * @param req The modify request.
1343 * @return and LDB status code.
1345 static int group_add(
1346 struct ldb_module *module,
1347 struct ldb_request *req)
1350 struct audit_context *ac =
1352 ldb_module_get_private(module),
1353 struct audit_context);
1355 * Currently we don't log replicated group changes
1357 if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
1358 return ldb_next_request(module, req);
1361 if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL) ||
1362 CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
1363 (ac->msg_ctx && ac->send_events)) {
1365 * Avoid the overheads of logging unless it has been
1368 if (has_group_membership_changes(req)) {
1369 return set_group_membership_add_callback(module, req);
1371 if (has_primary_group_id(req)) {
1372 return set_primary_group_add_callback(module, req);
1375 return ldb_next_request(module, req);
1379 * @brief Module handler for delete operations.
1381 * Currently there is no logging for delete operations.
1383 * @param module The ldb module.
1384 * @param req The modify request.
1386 * @return and LDB status code.
1388 static int group_delete(
1389 struct ldb_module *module,
1390 struct ldb_request *req)
1392 return ldb_next_request(module, req);
1396 * @brief Install the callback function to log a modify request.
1398 * Install the callback function to log a modify request. As we want to log the
1399 * returned status code, we need to register a callback function that will be
1400 * called once the operation has completed.
1402 * This function reads the current user record so that we can log the before
1405 * @param module The ldb module.
1406 * @param req The modify request.
1408 * @return and LDB status code.
1410 static int set_group_modify_callback(
1411 struct ldb_module *module,
1412 struct ldb_request *req)
1414 struct audit_callback_context *context = NULL;
1415 struct ldb_request *new_req = NULL;
1416 struct ldb_context *ldb = NULL;
1417 struct ldb_result *res = NULL;
1420 ldb = ldb_module_get_ctx(module);
1421 context = talloc_zero(req, struct audit_callback_context);
1423 if (context == NULL) {
1424 return ldb_oom(ldb);
1426 context->request = req;
1427 context->module = module;
1428 context->log_changes = log_group_membership_changes;
1431 * About to change the group memberships need to read
1432 * the current state from the database.
1434 ret = dsdb_module_search_dn(
1438 req->op.add.message->dn,
1440 DSDB_FLAG_NEXT_MODULE |
1441 DSDB_SEARCH_REVEAL_INTERNALS |
1442 DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
1444 if (ret == LDB_SUCCESS) {
1445 context->members = ldb_msg_find_element(res->msgs[0], "member");
1448 ret = ldb_build_mod_req(
1452 req->op.mod.message,
1455 group_audit_callback,
1457 if (ret != LDB_SUCCESS) {
1460 return ldb_next_request(module, new_req);
1464 * @brief Module handler for modify operations.
1466 * Inspect the current modify request, and if needed log any group membership
1469 * @param module The ldb module.
1470 * @param req The modify request.
1472 * @return and LDB status code.
1474 static int group_modify(
1475 struct ldb_module *module,
1476 struct ldb_request *req)
1479 struct audit_context *ac =
1481 ldb_module_get_private(module),
1482 struct audit_context);
1484 * Currently we don't log replicated group changes
1486 if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
1487 return ldb_next_request(module, req);
1490 if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL) ||
1491 CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
1492 (ac->msg_ctx && ac->send_events)) {
1494 * Avoid the overheads of logging unless it has been
1497 if (has_group_membership_changes(req)) {
1498 return set_group_modify_callback(module, req);
1500 if (has_primary_group_id(req)) {
1501 return set_primary_group_modify_callback(module, req);
1504 return ldb_next_request(module, req);
1508 * @brief ldb module initialisation
1510 * Initialise the module, loading the private data etc.
1512 * @param module The ldb module to initialise.
1514 * @return An LDB status code.
1516 static int group_init(struct ldb_module *module)
1519 struct ldb_context *ldb = ldb_module_get_ctx(module);
1520 struct audit_context *context = NULL;
1521 struct loadparm_context *lp_ctx
1522 = talloc_get_type_abort(
1523 ldb_get_opaque(ldb, "loadparm"),
1524 struct loadparm_context);
1525 struct tevent_context *ev = ldb_get_event_context(ldb);
1527 context = talloc_zero(module, struct audit_context);
1528 if (context == NULL) {
1529 return ldb_module_oom(module);
1532 if (lp_ctx && lpcfg_dsdb_group_change_notification(lp_ctx)) {
1533 context->send_events = true;
1534 context->msg_ctx = imessaging_client_init(context,
1539 ldb_module_set_private(module, context);
1540 return ldb_next_init(module);
1543 static const struct ldb_module_ops ldb_group_audit_log_module_ops = {
1544 .name = "group_audit_log",
1546 .modify = group_modify,
1547 .del = group_delete,
1548 .init_context = group_init,
1551 int ldb_group_audit_log_module_init(const char *version)
1553 LDB_MODULE_CHECK_VERSION(version);
1554 return ldb_register_module(&ldb_group_audit_log_module_ops);