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)
294 struct parsed_dn *pdn = NULL;
298 if (el == NULL || el->num_values == 0) {
302 pdn = talloc_zero_array(mem_ctx, struct parsed_dn, el->num_values);
304 DBG_ERR("Out of memory\n");
308 for (i = 0; i < el->num_values; i++) {
309 pdn[i].v = &el->values[i];
315 enum dn_compare_result {
322 * @brief compare parsed_dn, using GUID ordering
324 * Compare two parsed_dn structures, using GUID ordering.
325 * To avoid the overhead of parsing the DN's this function does a binary
326 * compare first. The DN's tre only parsed if they are not equal at a binary
329 * @param ctx talloc context that will own the parsed dsdb_dn
330 * @param ldb ldb_context
331 * @param dn1 The first dn
332 * @param dn2 The second dn
334 * @return BINARY_EQUAL values are equal at a binary level
335 * EQUAL DN's are equal but the meta data is different
336 * LESS_THAN dn1's GUID is less than dn2's GUID
337 * GREATER_THAN dn1's GUID is greater than dn2's GUID
340 static enum dn_compare_result dn_compare(
342 struct ldb_context *ldb,
343 struct parsed_dn *dn1,
344 struct parsed_dn *dn2) {
349 * Do a binary compare first to avoid unnecessary parsing
351 if (data_blob_cmp(dn1->v, dn2->v) == 0) {
353 * Values are equal at a binary level so no need
354 * for further processing
359 * Values not equal at the binary level, so lets
360 * do a GUID ordering compare. To do this we will need to ensure
361 * that the dn's have been parsed.
363 if (dn1->dsdb_dn == NULL) {
364 really_parse_trusted_dn(
370 if (dn2->dsdb_dn == NULL) {
371 really_parse_trusted_dn(
378 res = ndr_guid_compare(&dn1->guid, &dn2->guid);
381 } else if (res == 0) {
389 * @brief Get the DN of a users primary group as a printable string.
391 * Get the DN of a users primary group as a printable string.
393 * @param mem_ctx Talloc context the the returned string will be allocated on.
394 * @param module The ldb module
395 * @param account_sid The SID for the uses account.
396 * @param primary_group_rid The RID for the users primary group.
398 * @return a formatted DN, or null if there is an error.
400 static const char *get_primary_group_dn(
402 struct ldb_module *module,
403 struct dom_sid *account_sid,
404 uint32_t primary_group_rid)
408 struct ldb_context *ldb = NULL;
409 struct dom_sid *domain_sid = NULL;
410 struct dom_sid *primary_group_sid = NULL;
412 struct ldb_dn *dn = NULL;
413 struct ldb_message *msg = NULL;
416 ldb = ldb_module_get_ctx(module);
418 status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
419 if (!NT_STATUS_IS_OK(status)) {
423 primary_group_sid = dom_sid_add_rid(
427 if (!primary_group_sid) {
431 sid = dom_sid_string(mem_ctx, primary_group_sid);
436 dn = ldb_dn_new_fmt(mem_ctx, ldb, "<SID=%s>", sid);
440 rc = dsdb_search_one(
449 if (rc != LDB_SUCCESS) {
453 return ldb_dn_get_linearized(msg->dn);
457 * @brief Log details of a change to a users primary group.
459 * Log details of a change to a users primary group.
460 * There is no windows event id associated with a Primary Group change.
461 * However for a new user we generate an added to group event.
463 * @param module The ldb module.
464 * @param request The request being logged.
465 * @param action Description of the action being performed.
466 * @param group The linearized for of the group DN
467 * @param status the LDB status code for the processing of the request.
470 static void log_primary_group_change(
471 struct ldb_module *module,
472 const struct ldb_request *request,
477 const char *user = NULL;
479 struct audit_context *ac =
481 ldb_module_get_private(module),
482 struct audit_context);
484 TALLOC_CTX *ctx = talloc_new(NULL);
486 user = dsdb_audit_get_primary_dn(request);
487 if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL)) {
488 char *message = NULL;
489 message = audit_group_human_readable(
497 audit_log_human_text(
500 DBGC_DSDB_GROUP_AUDIT,
502 TALLOC_FREE(message);
505 if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
506 (ac->msg_ctx && ac->send_events)) {
508 struct json_object json;
509 json = audit_group_json(
510 module, request, action, user, group, EVT_ID_NONE, status);
513 DBGC_DSDB_GROUP_AUDIT_JSON,
515 if (ac->send_events) {
518 DSDB_GROUP_EVENT_NAME,
523 if (request->operation == LDB_ADD) {
525 * Have just added a user, generate a groupChange
526 * message indicating the user has been added to thier
535 * @brief Log details of a single change to a users group membership.
537 * Log details of a change to a users group membership, except for changes
538 * to their primary group which is handled by log_primary_group_change.
540 * @param module The ldb module.
541 * @param request The request being logged.
542 * @param action Description of the action being performed.
543 * @param user The linearized form of the users DN
544 * @param status the LDB status code for the processing of the request.
547 static void log_membership_change(struct ldb_module *module,
548 const struct ldb_request *request,
551 const enum event_id_type event_id,
554 const char *group = NULL;
555 struct audit_context *ac =
557 ldb_module_get_private(module),
558 struct audit_context);
560 TALLOC_CTX *ctx = talloc_new(NULL);
561 group = dsdb_audit_get_primary_dn(request);
562 if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL)) {
563 char *message = NULL;
564 message = audit_group_human_readable(
572 audit_log_human_text(
575 DBGC_DSDB_GROUP_AUDIT,
577 TALLOC_FREE(message);
580 if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
581 (ac->msg_ctx && ac->send_events)) {
582 struct json_object json;
583 json = audit_group_json(
584 module, request, action, user, group, event_id, status);
587 DBGC_DSDB_GROUP_AUDIT_JSON,
589 if (ac->send_events) {
592 DSDB_GROUP_EVENT_NAME,
602 * @brief Get the windows event type id for removing a user from a group type.
604 * @param group_type the type of the current group, see libds/common/flags.h
606 * @return the Windows Event Id
609 static enum event_id_type get_remove_member_event(uint32_t group_type)
612 switch (group_type) {
613 case GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
614 return EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP;
615 case GTYPE_SECURITY_GLOBAL_GROUP:
616 return EVT_ID_USER_REMOVED_FROM_GLOBAL_SEC_GROUP;
617 case GTYPE_SECURITY_DOMAIN_LOCAL_GROUP:
618 return EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP;
619 case GTYPE_SECURITY_UNIVERSAL_GROUP:
620 return EVT_ID_USER_REMOVED_FROM_UNIVERSAL_SEC_GROUP;
621 case GTYPE_DISTRIBUTION_GLOBAL_GROUP:
622 return EVT_ID_USER_REMOVED_FROM_GLOBAL_GROUP;
623 case GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP:
624 return EVT_ID_USER_REMOVED_FROM_LOCAL_GROUP;
625 case GTYPE_DISTRIBUTION_UNIVERSAL_GROUP:
626 return EVT_ID_USER_REMOVED_FROM_UNIVERSAL_GROUP;
633 * @brief Get the windows event type id for adding a user to a group type.
635 * @param group_type the type of the current group, see libds/common/flags.h
637 * @return the Windows Event Id
640 static enum event_id_type get_add_member_event(uint32_t group_type)
643 switch (group_type) {
644 case GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
645 return EVT_ID_USER_ADDED_TO_LOCAL_SEC_GROUP;
646 case GTYPE_SECURITY_GLOBAL_GROUP:
647 return EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP;
648 case GTYPE_SECURITY_DOMAIN_LOCAL_GROUP:
649 return EVT_ID_USER_ADDED_TO_LOCAL_SEC_GROUP;
650 case GTYPE_SECURITY_UNIVERSAL_GROUP:
651 return EVT_ID_USER_ADDED_TO_UNIVERSAL_SEC_GROUP;
652 case GTYPE_DISTRIBUTION_GLOBAL_GROUP:
653 return EVT_ID_USER_ADDED_TO_GLOBAL_GROUP;
654 case GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP:
655 return EVT_ID_USER_ADDED_TO_LOCAL_GROUP;
656 case GTYPE_DISTRIBUTION_UNIVERSAL_GROUP:
657 return EVT_ID_USER_ADDED_TO_UNIVERSAL_GROUP;
664 * @brief Log all the changes to a users group membership.
666 * Log details of a change to a users group memberships, except for changes
667 * to their primary group which is handled by log_primary_group_change.
669 * @param module The ldb module.
670 * @param request The request being logged.
671 * @param action Description of the action being performed.
672 * @param user The linearized form of the users DN
673 * @param status the LDB status code for the processing of the request.
676 static void log_membership_changes(struct ldb_module *module,
677 const struct ldb_request *request,
678 struct ldb_message_element *el,
679 struct ldb_message_element *old_el,
683 unsigned int i, old_i, new_i;
684 unsigned int old_num_values;
685 unsigned int max_num_values;
686 unsigned int new_num_values;
687 struct parsed_dn *old_val = NULL;
688 struct parsed_dn *new_val = NULL;
689 struct parsed_dn *new_values = NULL;
690 struct parsed_dn *old_values = NULL;
691 struct ldb_context *ldb = NULL;
693 TALLOC_CTX *ctx = talloc_new(NULL);
695 old_num_values = old_el ? old_el->num_values : 0;
696 new_num_values = el ? el->num_values : 0;
697 max_num_values = old_num_values + new_num_values;
699 if (max_num_values == 0) {
701 * There is nothing to do!
707 old_values = get_parsed_dns(ctx, old_el);
708 new_values = get_parsed_dns(ctx, el);
709 ldb = ldb_module_get_ctx(module);
713 for (i = 0; i < max_num_values; i++) {
714 enum dn_compare_result cmp;
715 if (old_i < old_num_values && new_i < new_num_values) {
717 * Both list have values, so compare the values
719 old_val = &old_values[old_i];
720 new_val = &new_values[new_i];
721 cmp = dn_compare(ctx, ldb, old_val, new_val);
722 } else if (old_i < old_num_values) {
724 * the new list is empty, read the old list
726 old_val = &old_values[old_i];
729 } else if (new_i < new_num_values) {
731 * the old list is empty, read new list
734 new_val = &new_values[new_i];
740 if (cmp == LESS_THAN) {
742 * Have an entry in the original record that is not in
743 * the new record. So it's been deleted
745 const char *user = NULL;
746 enum event_id_type event_id;
747 if (old_val->dsdb_dn == NULL) {
748 really_parse_trusted_dn(
754 user = ldb_dn_get_linearized(old_val->dsdb_dn->dn);
755 event_id = get_remove_member_event(group_type);
756 log_membership_change(
757 module, request, "Removed", user, event_id, status);
759 } else if (cmp == BINARY_EQUAL) {
761 * DN's unchanged at binary level so nothing to do.
765 } else if (cmp == EQUAL) {
767 * DN is unchanged now need to check the flags to
768 * determine if a record has been deleted or undeleted
772 if (old_val->dsdb_dn == NULL) {
773 really_parse_trusted_dn(
779 if (new_val->dsdb_dn == NULL) {
780 really_parse_trusted_dn(
787 dsdb_get_extended_dn_uint32(
788 old_val->dsdb_dn->dn,
791 dsdb_get_extended_dn_uint32(
792 new_val->dsdb_dn->dn,
795 if (new_flags == old_flags) {
797 * No changes to the Repl meta data so can
798 * no need to log the change
804 if (new_flags & DSDB_RMD_FLAG_DELETED) {
806 * DN has been deleted.
808 const char *user = NULL;
809 enum event_id_type event_id;
810 user = ldb_dn_get_linearized(
811 old_val->dsdb_dn->dn);
812 event_id = get_remove_member_event(group_type);
813 log_membership_change(module,
821 * DN has been re-added
823 const char *user = NULL;
824 enum event_id_type event_id;
825 user = ldb_dn_get_linearized(
826 new_val->dsdb_dn->dn);
827 event_id = get_add_member_event(group_type);
828 log_membership_change(module,
839 * Member in the updated record that's not in the
840 * original, so it must have been added.
842 const char *user = NULL;
843 enum event_id_type event_id;
844 if ( new_val->dsdb_dn == NULL) {
845 really_parse_trusted_dn(
851 user = ldb_dn_get_linearized(new_val->dsdb_dn->dn);
852 event_id = get_add_member_event(group_type);
853 log_membership_change(
854 module, request, "Added", user, event_id, status);
863 * @brief log a group change message for a newly added user.
865 * When a user is added we need to generate a GroupChange Add message to
866 * log that the user has been added to their PrimaryGroup
868 static void log_new_user_added_to_primary_group(
870 struct audit_callback_context *acc,
875 enum event_id_type event_id = EVT_ID_NONE;
876 struct ldb_result *res = NULL;
877 struct ldb_dn *group_dn = NULL;
878 struct ldb_context *ldb = NULL;
881 ldb = ldb_module_get_ctx(acc->module);
882 group_dn = ldb_dn_new(ctx, ldb, group);
883 ret = dsdb_module_search_dn(acc->module,
888 DSDB_FLAG_NEXT_MODULE |
889 DSDB_SEARCH_REVEAL_INTERNALS |
890 DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
892 if (ret == LDB_SUCCESS) {
893 const char *user = NULL;
895 ldb_msg_find_attr_as_uint(res->msgs[0], "groupType", 0);
896 event_id = get_add_member_event(group_type);
897 user = dsdb_audit_get_primary_dn(acc->request);
898 log_membership_change(
899 acc->module, acc->request, "Added", user, event_id, status);
904 * @brief Log the details of a primary group change.
906 * Retrieve the users primary groupo after the operation has completed
907 * and call log_primary_group_change to log the actual changes.
909 * @param acc details of the primary group before the operation.
910 * @param status The status code returned by the operation.
912 * @return an LDB status code.
914 static void log_user_primary_group_change(
915 struct audit_callback_context *acc,
918 TALLOC_CTX *ctx = talloc_new(NULL);
920 struct dom_sid *account_sid = NULL;
922 const struct ldb_message *msg = dsdb_audit_get_message(acc->request);
924 if (status == LDB_SUCCESS && msg != NULL) {
925 struct ldb_result *res = NULL;
926 ret = dsdb_module_search_dn(
932 DSDB_FLAG_NEXT_MODULE |
933 DSDB_SEARCH_REVEAL_INTERNALS |
934 DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
936 if (ret == LDB_SUCCESS) {
937 new_rid = ldb_msg_find_attr_as_uint(
941 account_sid = samdb_result_dom_sid(
948 * If we don't have a new value then the user has been deleted
949 * which we currently do not log.
950 * Otherwise only log if the primary group has actually changed.
952 if (account_sid != NULL &&
954 acc->primary_group != new_rid) {
955 const char* group = get_primary_group_dn(
960 log_primary_group_change(
967 * Are we adding a new user with the primaryGroupID
968 * set. If so and we're generating JSON audit logs, will need to
969 * generate an "Add" message with the appropriate windows
972 if (acc->request->operation == LDB_ADD) {
973 log_new_user_added_to_primary_group(
974 ctx, acc, group, status);
981 * @brief log the changes to users group membership.
983 * Retrieve the users group memberships after the operation has completed
984 * and call log_membership_changes to log the actual changes.
986 * @param acc details of the group memberships before the operation.
987 * @param status The status code returned by the operation.
990 static void log_group_membership_changes(
991 struct audit_callback_context *acc,
994 TALLOC_CTX *ctx = talloc_new(NULL);
995 struct ldb_message_element *new_val = NULL;
997 uint32_t group_type = 0;
998 const struct ldb_message *msg = dsdb_audit_get_message(acc->request);
999 if (status == LDB_SUCCESS && msg != NULL) {
1000 struct ldb_result *res = NULL;
1001 ret = dsdb_module_search_dn(
1007 DSDB_FLAG_NEXT_MODULE |
1008 DSDB_SEARCH_REVEAL_INTERNALS |
1009 DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
1011 if (ret == LDB_SUCCESS) {
1012 new_val = ldb_msg_find_element(res->msgs[0], "member");
1013 group_type = ldb_msg_find_attr_as_uint(
1014 res->msgs[0], "groupType", 0);
1017 log_membership_changes(acc->module,
1027 * @brief call back function to log changes to the group memberships.
1029 * Call back function to log changes to the uses broup memberships.
1031 * @param req the ldb request.
1032 * @param ares the ldb result
1034 * @return am LDB status code.
1036 static int group_audit_callback(
1037 struct ldb_request *req,
1038 struct ldb_reply *ares)
1040 struct audit_callback_context *ac = NULL;
1042 ac = talloc_get_type(
1044 struct audit_callback_context);
1047 return ldb_module_done(
1048 ac->request, NULL, NULL,
1049 LDB_ERR_OPERATIONS_ERROR);
1052 /* pass on to the callback */
1053 switch (ares->type) {
1054 case LDB_REPLY_ENTRY:
1055 return ldb_module_send_entry(
1060 case LDB_REPLY_REFERRAL:
1061 return ldb_module_send_referral(
1065 case LDB_REPLY_DONE:
1067 * Log on DONE now we have a result code
1069 ac->log_changes(ac, ares->error);
1070 return ldb_module_done(
1079 return LDB_ERR_OPERATIONS_ERROR;
1084 * @brief Does this request change the primary group.
1086 * Does the request change the primary group, i.e. does it contain the
1087 * primaryGroupID attribute.
1089 * @param req the request to examine.
1091 * @return True if the request modifies the primary group.
1093 static bool has_primary_group_id(struct ldb_request *req)
1095 struct ldb_message_element *el = NULL;
1096 const struct ldb_message *msg = NULL;
1098 msg = dsdb_audit_get_message(req);
1099 el = ldb_msg_find_element(msg, "primaryGroupID");
1101 return (el != NULL);
1105 * @brief Does this request change group membership.
1107 * Does the request change the ses group memberships, i.e. does it contain the
1110 * @param req the request to examine.
1112 * @return True if the request modifies the users group memberships.
1114 static bool has_group_membership_changes(struct ldb_request *req)
1116 struct ldb_message_element *el = NULL;
1117 const struct ldb_message *msg = NULL;
1119 msg = dsdb_audit_get_message(req);
1120 el = ldb_msg_find_element(msg, "member");
1122 return (el != NULL);
1128 * @brief Install the callback function to log an add request.
1130 * Install the callback function to log an add request changing the users
1131 * group memberships. As we want to log the returned status code, we need to
1132 * register a callback function that will be called once the operation has
1135 * This function reads the current user record so that we can log the before
1138 * @param module The ldb module.
1139 * @param req The modify request.
1141 * @return and LDB status code.
1143 static int set_group_membership_add_callback(
1144 struct ldb_module *module,
1145 struct ldb_request *req)
1147 struct audit_callback_context *context = NULL;
1148 struct ldb_request *new_req = NULL;
1149 struct ldb_context *ldb = NULL;
1152 * Adding group memberships so will need to log the changes.
1154 ldb = ldb_module_get_ctx(module);
1155 context = talloc_zero(req, struct audit_callback_context);
1157 if (context == NULL) {
1158 return ldb_oom(ldb);
1160 context->request = req;
1161 context->module = module;
1162 context->log_changes = log_group_membership_changes;
1164 * We want to log the return code status, so we need to register
1165 * a callback function to get the actual result.
1166 * We need to take a new copy so that we don't alter the callers copy
1168 ret = ldb_build_add_req(
1172 req->op.add.message,
1175 group_audit_callback,
1177 if (ret != LDB_SUCCESS) {
1180 return ldb_next_request(module, new_req);
1185 * @brief Install the callback function to log a modify request.
1187 * Install the callback function to log a modify request changing the primary
1188 * group . As we want to log the returned status code, we need to register a
1189 * callback function that will be called once the operation has completed.
1191 * This function reads the current user record so that we can log the before
1194 * @param module The ldb module.
1195 * @param req The modify request.
1197 * @return and LDB status code.
1199 static int set_primary_group_modify_callback(
1200 struct ldb_module *module,
1201 struct ldb_request *req)
1203 struct audit_callback_context *context = NULL;
1204 struct ldb_request *new_req = NULL;
1205 struct ldb_context *ldb = NULL;
1206 const struct ldb_message *msg = NULL;
1207 struct ldb_result *res = NULL;
1210 TALLOC_CTX *ctx = talloc_new(NULL);
1212 ldb = ldb_module_get_ctx(module);
1214 context = talloc_zero(req, struct audit_callback_context);
1215 if (context == NULL) {
1219 context->request = req;
1220 context->module = module;
1221 context->log_changes = log_user_primary_group_change;
1223 msg = dsdb_audit_get_message(req);
1224 ret = dsdb_module_search_dn(
1230 DSDB_FLAG_NEXT_MODULE |
1231 DSDB_SEARCH_REVEAL_INTERNALS |
1232 DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
1234 if (ret == LDB_SUCCESS) {
1236 pg = ldb_msg_find_attr_as_uint(
1240 context->primary_group = pg;
1243 * We want to log the return code status, so we need to register
1244 * a callback function to get the actual result.
1245 * We need to take a new copy so that we don't alter the callers copy
1247 ret = ldb_build_mod_req(
1251 req->op.add.message,
1254 group_audit_callback,
1256 if (ret != LDB_SUCCESS) {
1259 ret = ldb_next_request(module, new_req);
1266 * @brief Install the callback function to log an add request.
1268 * Install the callback function to log an add request changing the primary
1269 * group . As we want to log the returned status code, we need to register a
1270 * callback function that will be called once the operation has completed.
1272 * This function reads the current user record so that we can log the before
1275 * @param module The ldb module.
1276 * @param req The modify request.
1278 * @return and LDB status code.
1280 static int set_primary_group_add_callback(
1281 struct ldb_module *module,
1282 struct ldb_request *req)
1284 struct audit_callback_context *context = NULL;
1285 struct ldb_request *new_req = NULL;
1286 struct ldb_context *ldb = NULL;
1289 * Adding a user with a primary group.
1291 ldb = ldb_module_get_ctx(module);
1292 context = talloc_zero(req, struct audit_callback_context);
1294 if (context == NULL) {
1295 return ldb_oom(ldb);
1297 context->request = req;
1298 context->module = module;
1299 context->log_changes = log_user_primary_group_change;
1301 * We want to log the return code status, so we need to register
1302 * a callback function to get the actual result.
1303 * We need to take a new copy so that we don't alter the callers copy
1305 ret = ldb_build_add_req(
1309 req->op.add.message,
1312 group_audit_callback,
1314 if (ret != LDB_SUCCESS) {
1317 return ldb_next_request(module, new_req);
1321 * @brief Module handler for add operations.
1323 * Inspect the current add request, and if needed log any group membership
1326 * @param module The ldb module.
1327 * @param req The modify request.
1329 * @return and LDB status code.
1331 static int group_add(
1332 struct ldb_module *module,
1333 struct ldb_request *req)
1336 struct audit_context *ac =
1338 ldb_module_get_private(module),
1339 struct audit_context);
1341 * Currently we don't log replicated group changes
1343 if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
1344 return ldb_next_request(module, req);
1347 if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL) ||
1348 CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
1349 (ac->msg_ctx && ac->send_events)) {
1351 * Avoid the overheads of logging unless it has been
1354 if (has_group_membership_changes(req)) {
1355 return set_group_membership_add_callback(module, req);
1357 if (has_primary_group_id(req)) {
1358 return set_primary_group_add_callback(module, req);
1361 return ldb_next_request(module, req);
1365 * @brief Module handler for delete operations.
1367 * Currently there is no logging for delete operations.
1369 * @param module The ldb module.
1370 * @param req The modify request.
1372 * @return and LDB status code.
1374 static int group_delete(
1375 struct ldb_module *module,
1376 struct ldb_request *req)
1378 return ldb_next_request(module, req);
1382 * @brief Install the callback function to log a modify request.
1384 * Install the callback function to log a modify request. As we want to log the
1385 * returned status code, we need to register a callback function that will be
1386 * called once the operation has completed.
1388 * This function reads the current user record so that we can log the before
1391 * @param module The ldb module.
1392 * @param req The modify request.
1394 * @return and LDB status code.
1396 static int set_group_modify_callback(
1397 struct ldb_module *module,
1398 struct ldb_request *req)
1400 struct audit_callback_context *context = NULL;
1401 struct ldb_request *new_req = NULL;
1402 struct ldb_context *ldb = NULL;
1403 struct ldb_result *res = NULL;
1406 ldb = ldb_module_get_ctx(module);
1407 context = talloc_zero(req, struct audit_callback_context);
1409 if (context == NULL) {
1410 return ldb_oom(ldb);
1412 context->request = req;
1413 context->module = module;
1414 context->log_changes = log_group_membership_changes;
1417 * About to change the group memberships need to read
1418 * the current state from the database.
1420 ret = dsdb_module_search_dn(
1424 req->op.add.message->dn,
1426 DSDB_FLAG_NEXT_MODULE |
1427 DSDB_SEARCH_REVEAL_INTERNALS |
1428 DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
1430 if (ret == LDB_SUCCESS) {
1431 context->members = ldb_msg_find_element(res->msgs[0], "member");
1434 ret = ldb_build_mod_req(
1438 req->op.mod.message,
1441 group_audit_callback,
1443 if (ret != LDB_SUCCESS) {
1446 return ldb_next_request(module, new_req);
1450 * @brief Module handler for modify operations.
1452 * Inspect the current modify request, and if needed log any group membership
1455 * @param module The ldb module.
1456 * @param req The modify request.
1458 * @return and LDB status code.
1460 static int group_modify(
1461 struct ldb_module *module,
1462 struct ldb_request *req)
1465 struct audit_context *ac =
1467 ldb_module_get_private(module),
1468 struct audit_context);
1470 * Currently we don't log replicated group changes
1472 if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
1473 return ldb_next_request(module, req);
1476 if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL) ||
1477 CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
1478 (ac->msg_ctx && ac->send_events)) {
1480 * Avoid the overheads of logging unless it has been
1483 if (has_group_membership_changes(req)) {
1484 return set_group_modify_callback(module, req);
1486 if (has_primary_group_id(req)) {
1487 return set_primary_group_modify_callback(module, req);
1490 return ldb_next_request(module, req);
1494 * @brief ldb module initialisation
1496 * Initialise the module, loading the private data etc.
1498 * @param module The ldb module to initialise.
1500 * @return An LDB status code.
1502 static int group_init(struct ldb_module *module)
1505 struct ldb_context *ldb = ldb_module_get_ctx(module);
1506 struct audit_context *context = NULL;
1507 struct loadparm_context *lp_ctx
1508 = talloc_get_type_abort(
1509 ldb_get_opaque(ldb, "loadparm"),
1510 struct loadparm_context);
1511 struct tevent_context *ev = ldb_get_event_context(ldb);
1513 context = talloc_zero(module, struct audit_context);
1514 if (context == NULL) {
1515 return ldb_module_oom(module);
1518 if (lp_ctx && lpcfg_dsdb_group_change_notification(lp_ctx)) {
1519 context->send_events = true;
1520 context->msg_ctx = imessaging_client_init(context,
1525 ldb_module_set_private(module, context);
1526 return ldb_next_init(module);
1529 static const struct ldb_module_ops ldb_group_audit_log_module_ops = {
1530 .name = "group_audit_log",
1532 .modify = group_modify,
1533 .del = group_delete,
1534 .init_context = group_init,
1537 int ldb_group_audit_log_module_init(const char *version)
1539 LDB_MODULE_CHECK_VERSION(version);
1540 return ldb_register_module(&ldb_group_audit_log_module_ops);