// SPDX-License-Identifier: GPL-3.0-or-later #include "windows-events.h" //struct { // const char *name; // const wchar_t *query; //} custom_queries[] = { // { // .name = "All-Administrative-Events", // .query = L"\n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // "", // }, // { // .name = "All-Remote-Desktop-Services", // .query = L"\n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // " \n" // "", // }, // { // .name = "All-Security-SPP", // .query = L"\n" // " \n" // " \n" // " \n" // "", // } //}; ENUM_STR_MAP_DEFINE(WEVT_SOURCE_TYPE) = { { .id = WEVTS_ALL, .name = WEVT_SOURCE_ALL_NAME }, { .id = WEVTS_ADMIN, .name = WEVT_SOURCE_ALL_ADMIN_NAME }, { .id = WEVTS_OPERATIONAL, .name = WEVT_SOURCE_ALL_OPERATIONAL_NAME }, { .id = WEVTS_ANALYTIC, .name = WEVT_SOURCE_ALL_ANALYTIC_NAME }, { .id = WEVTS_DEBUG, .name = WEVT_SOURCE_ALL_DEBUG_NAME }, { .id = WEVTS_WINDOWS, .name = WEVT_SOURCE_ALL_WINDOWS_NAME }, { .id = WEVTS_ENABLED, .name = WEVT_SOURCE_ALL_ENABLED_NAME }, { .id = WEVTS_DISABLED, .name = WEVT_SOURCE_ALL_DISABLED_NAME }, { .id = WEVTS_FORWARDED, .name = WEVT_SOURCE_ALL_FORWARDED_NAME }, { .id = WEVTS_CLASSIC, .name = WEVT_SOURCE_ALL_CLASSIC_NAME }, { .id = WEVTS_BACKUP_MODE, .name = WEVT_SOURCE_ALL_BACKUP_MODE_NAME }, { .id = WEVTS_OVERWRITE_MODE, .name = WEVT_SOURCE_ALL_OVERWRITE_MODE_NAME }, { .id = WEVTS_STOP_WHEN_FULL_MODE, .name = WEVT_SOURCE_ALL_STOP_WHEN_FULL_MODE_NAME }, { .id = WEVTS_RETAIN_AND_BACKUP_MODE, .name = WEVT_SOURCE_ALL_RETAIN_AND_BACKUP_MODE_NAME }, // terminator { . id = 0, .name = NULL } }; BITMAP_STR_DEFINE_FUNCTIONS(WEVT_SOURCE_TYPE, WEVTS_NONE, ""); DICTIONARY *wevt_sources = NULL; DICTIONARY *used_hashes_registry = NULL; static usec_t wevt_session = 0; void wevt_sources_del_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) { LOGS_QUERY_SOURCE *src = value; freez((void *)src->fullname); string_freez(src->source); src->fullname = NULL; src->source = NULL; } static bool wevt_sources_conflict_cb(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data __maybe_unused) { LOGS_QUERY_SOURCE *src_old = old_value; LOGS_QUERY_SOURCE *src_new = new_value; bool ret = false; if(src_new->last_scan_monotonic_ut > src_old->last_scan_monotonic_ut) { src_old->last_scan_monotonic_ut = src_new->last_scan_monotonic_ut; if (src_old->source != src_new->source) { string_freez(src_old->source); src_old->source = src_new->source; src_new->source = NULL; } src_old->source_type = src_new->source_type; src_old->msg_first_ut = src_new->msg_first_ut; src_old->msg_last_ut = src_new->msg_last_ut; src_old->msg_first_id = src_new->msg_first_id; src_old->msg_last_id = src_new->msg_last_id; src_old->entries = src_new->entries; src_old->size = src_new->size; ret = true; } freez((void *)src_new->fullname); string_freez(src_new->source); src_new->fullname = NULL; src_new->source = NULL; return ret; } void wevt_sources_init(void) { wevt_session = now_realtime_usec(); used_hashes_registry = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE); wevt_sources = dictionary_create_advanced(DICT_OPTION_FIXED_SIZE | DICT_OPTION_DONT_OVERWRITE_VALUE, NULL, sizeof(LOGS_QUERY_SOURCE)); dictionary_register_delete_callback(wevt_sources, wevt_sources_del_cb, NULL); dictionary_register_conflict_callback(wevt_sources, wevt_sources_conflict_cb, NULL); } void buffer_json_wevt_versions(BUFFER *wb __maybe_unused) { buffer_json_member_add_object(wb, "versions"); { buffer_json_member_add_uint64(wb, "sources", wevt_session + dictionary_version(wevt_sources)); } buffer_json_object_close(wb); } // -------------------------------------------------------------------------------------------------------------------- int wevt_sources_dict_items_backward_compar(const void *a, const void *b) { const DICTIONARY_ITEM **da = (const DICTIONARY_ITEM **)a, **db = (const DICTIONARY_ITEM **)b; LOGS_QUERY_SOURCE *sa = dictionary_acquired_item_value(*da); LOGS_QUERY_SOURCE *sb = dictionary_acquired_item_value(*db); // compare the last message timestamps if(sa->msg_last_ut < sb->msg_last_ut) return 1; if(sa->msg_last_ut > sb->msg_last_ut) return -1; // compare the first message timestamps if(sa->msg_first_ut < sb->msg_first_ut) return 1; if(sa->msg_first_ut > sb->msg_first_ut) return -1; return 0; } int wevt_sources_dict_items_forward_compar(const void *a, const void *b) { return -wevt_sources_dict_items_backward_compar(a, b); } // -------------------------------------------------------------------------------------------------------------------- typedef enum { wevt_source_type_internal, wevt_source_type_provider, wevt_source_type_channel, } wevt_source_type; struct wevt_source { wevt_source_type type; usec_t first_ut; usec_t last_ut; size_t count; size_t entries; uint64_t size; }; static int wevt_source_to_json_array_cb(const DICTIONARY_ITEM *item, void *entry, void *data) { const struct wevt_source *s = entry; BUFFER *wb = data; const char *name = dictionary_acquired_item_name(item); if(s->count == 1 && strncmp(name, WEVT_SOURCE_ALL_OF_PROVIDER_PREFIX, sizeof(WEVT_SOURCE_ALL_OF_PROVIDER_PREFIX) - 1) == 0) // do not include "All-Of-X" when there is only 1 channel return 0; bool default_selected = (s->type == wevt_source_type_channel); if(default_selected && (strcmp(name, "NetdataWEL") == 0 || strcmp(name, "Netdata/Access") == 0)) // do not select Netdata Access logs by default default_selected = false; buffer_json_add_array_item_object(wb); { char size_for_humans[128]; size_snprintf(size_for_humans, sizeof(size_for_humans), s->size, "B", false); char duration_for_humans[128]; duration_snprintf(duration_for_humans, sizeof(duration_for_humans), (time_t)((s->last_ut - s->first_ut) / USEC_PER_SEC), "s", true); char entries_for_humans[128]; entries_snprintf(entries_for_humans, sizeof(entries_for_humans), s->entries, "", false); char info[1024]; snprintfz(info, sizeof(info), "%zu channel%s, with a total size of %s, covering %s%s%s%s", s->count, s->count > 1 ? "s":"", size_for_humans, duration_for_humans, s->entries ? ", having " : "", s->entries ? entries_for_humans : "", s->entries ? " entries" : ""); buffer_json_member_add_string(wb, "id", name); buffer_json_member_add_string(wb, "name", name); buffer_json_member_add_string(wb, "pill", size_for_humans); buffer_json_member_add_string(wb, "info", info); buffer_json_member_add_boolean(wb, "default_selected", default_selected); } buffer_json_object_close(wb); // options object return 1; } static bool wevt_source_merge_sizes(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value , void *data __maybe_unused) { struct wevt_source *old_v = old_value; const struct wevt_source *new_v = new_value; old_v->count += new_v->count; old_v->size += new_v->size; old_v->entries += new_v->entries; if(new_v->first_ut && new_v->first_ut < old_v->first_ut) old_v->first_ut = new_v->first_ut; if(new_v->last_ut && new_v->last_ut > old_v->last_ut) old_v->last_ut = new_v->last_ut; return false; } void wevt_sources_to_json_array(BUFFER *wb) { DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_NAME_LINK_DONT_CLONE|DICT_OPTION_DONT_OVERWRITE_VALUE); dictionary_register_conflict_callback(dict, wevt_source_merge_sizes, NULL); struct wevt_source t = { 0 }; LOGS_QUERY_SOURCE *src; dfe_start_read(wevt_sources, src) { t.first_ut = src->msg_first_ut; t.last_ut = src->msg_last_ut; t.count = 1; t.size = src->size; t.entries = src->entries; src->source_type |= WEVTS_ALL; t.type = wevt_source_type_internal; for(size_t i = 0; WEVT_SOURCE_TYPE_names[i].name ;i++) { if(src->source_type & WEVT_SOURCE_TYPE_names[i].id) dictionary_set(dict, WEVT_SOURCE_TYPE_names[i].name, &t, sizeof(t)); } if(src->provider) { t.type = wevt_source_type_provider; dictionary_set(dict, string2str(src->provider), &t, sizeof(t)); } if(src->source) { t.type = wevt_source_type_channel; dictionary_set(dict, string2str(src->source), &t, sizeof(t)); } } dfe_done(jf); dictionary_sorted_walkthrough_read(dict, wevt_source_to_json_array_cb, wb); } static bool ndEvtGetChannelConfigProperty(EVT_HANDLE hChannelConfig, WEVT_VARIANT *pr, EVT_CHANNEL_CONFIG_PROPERTY_ID id) { if (!EvtGetChannelConfigProperty(hChannelConfig, id, 0, pr->size, pr->data, &pr->used)) { DWORD status = GetLastError(); if (ERROR_INSUFFICIENT_BUFFER == status) { wevt_variant_resize(pr, pr->used); if(!EvtGetChannelConfigProperty(hChannelConfig, id, 0, pr->size, pr->data, &pr->used)) { pr->used = 0; pr->count = 0; return false; } } } wevt_variant_count_from_used(pr); return true; } WEVT_SOURCE_TYPE categorize_channel(const wchar_t *channel_path, const char **provider, WEVT_VARIANT *property) { EVT_HANDLE hChannelConfig = NULL; WEVT_SOURCE_TYPE result = WEVTS_ALL; // Open the channel configuration hChannelConfig = EvtOpenChannelConfig(NULL, channel_path, 0); if (!hChannelConfig) goto cleanup; if(ndEvtGetChannelConfigProperty(hChannelConfig, property, EvtChannelConfigType) & property->count && property->data[0].Type == EvtVarTypeUInt32) { switch (property->data[0].UInt32Val) { case EvtChannelTypeAdmin: result |= WEVTS_ADMIN; break; case EvtChannelTypeOperational: result |= WEVTS_OPERATIONAL; break; case EvtChannelTypeAnalytic: result |= WEVTS_ANALYTIC; break; case EvtChannelTypeDebug: result |= WEVTS_DEBUG; break; default: break; } } if(ndEvtGetChannelConfigProperty(hChannelConfig, property, EvtChannelConfigClassicEventlog) && property->count && property->data[0].Type == EvtVarTypeBoolean && property->data[0].BooleanVal) result |= WEVTS_CLASSIC; if(ndEvtGetChannelConfigProperty(hChannelConfig, property, EvtChannelConfigOwningPublisher) && property->count && property->data[0].Type == EvtVarTypeString) { *provider = provider2utf8(property->data[0].StringVal); if(wcscasecmp(property->data[0].StringVal, L"Microsoft-Windows-EventCollector") == 0) result |= WEVTS_FORWARDED; } else *provider = NULL; if(ndEvtGetChannelConfigProperty(hChannelConfig, property, EvtChannelConfigEnabled) && property->count && property->data[0].Type == EvtVarTypeBoolean) { if(property->data[0].BooleanVal) result |= WEVTS_ENABLED; else result |= WEVTS_DISABLED; } bool got_retention = false; bool retained = false; if(ndEvtGetChannelConfigProperty(hChannelConfig, property, EvtChannelLoggingConfigRetention) && property->count && property->data[0].Type == EvtVarTypeBoolean) { got_retention = true; retained = property->data[0].BooleanVal; } bool got_auto_backup = false; bool auto_backup = false; if(ndEvtGetChannelConfigProperty(hChannelConfig, property, EvtChannelLoggingConfigAutoBackup) && property->count && property->data[0].Type == EvtVarTypeBoolean) { got_auto_backup = true; auto_backup = property->data[0].BooleanVal; } if(got_retention && got_auto_backup) { if(!retained) { if(auto_backup) result |= WEVTS_BACKUP_MODE; else result |= WEVTS_OVERWRITE_MODE; } else { if(auto_backup) result |= WEVTS_STOP_WHEN_FULL_MODE; else result |= WEVTS_RETAIN_AND_BACKUP_MODE; } } cleanup: if (hChannelConfig) EvtClose(hChannelConfig); return result; } void wevt_sources_scan(void) { static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER; LPWSTR channel = NULL; EVT_HANDLE hChannelEnum = NULL; if(spinlock_trylock(&spinlock)) { const usec_t started_ut = now_monotonic_usec(); WEVT_VARIANT property = { 0 }; DWORD dwChannelBufferSize = 0; DWORD dwChannelBufferUsed = 0; DWORD status = ERROR_SUCCESS; // Open a handle to enumerate the event channels hChannelEnum = EvtOpenChannelEnum(NULL, 0); if (!hChannelEnum) { nd_log(NDLS_COLLECTORS, NDLP_ERR, "WINDOWS EVENTS: EvtOpenChannelEnum() failed with %" PRIu64 "\n", (uint64_t)GetLastError()); goto cleanup; } WEVT_LOG *log = wevt_openlog6(WEVT_QUERY_RETENTION); if(!log) goto cleanup; while (true) { if (!EvtNextChannelPath(hChannelEnum, dwChannelBufferSize, channel, &dwChannelBufferUsed)) { status = GetLastError(); if (status == ERROR_NO_MORE_ITEMS) break; // No more channels else if (status == ERROR_INSUFFICIENT_BUFFER) { dwChannelBufferSize = dwChannelBufferUsed; freez(channel); channel = mallocz(dwChannelBufferSize * sizeof(WCHAR)); continue; } else { nd_log(NDLS_COLLECTORS, NDLP_ERR, "WINDOWS EVENTS: EvtNextChannelPath() failed\n"); break; } } EVT_RETENTION retention; if(!wevt_channel_retention(log, channel, NULL, &retention)) continue; LOGS_QUERY_SOURCE *found = dictionary_get(wevt_sources, channel2utf8(channel)); if(found) { // we just need to update its retention found->last_scan_monotonic_ut = now_monotonic_usec(); found->msg_first_id = retention.first_event.id; found->msg_last_id = retention.last_event.id; found->msg_first_ut = retention.first_event.created_ns / NSEC_PER_USEC; found->msg_last_ut = retention.last_event.created_ns / NSEC_PER_USEC; found->size = retention.size_bytes; continue; } const char *name = channel2utf8(channel); const char *fullname = strdupz(name); const char *provider; WEVT_SOURCE_TYPE sources = categorize_channel(channel, &provider, &property); char *slash = strchr(name, '/'); if(slash) *slash = '\0'; if(strcasecmp(name, "Application") == 0) sources |= WEVTS_WINDOWS; if(strcasecmp(name, "Security") == 0) sources |= WEVTS_WINDOWS; if(strcasecmp(name, "Setup") == 0) sources |= WEVTS_WINDOWS; if(strcasecmp(name, "System") == 0) sources |= WEVTS_WINDOWS; LOGS_QUERY_SOURCE src = { .entries = retention.entries, .fullname = fullname, .fullname_len = strlen(fullname), .last_scan_monotonic_ut = now_monotonic_usec(), .msg_first_id = retention.first_event.id, .msg_last_id = retention.last_event.id, .msg_first_ut = retention.first_event.created_ns / NSEC_PER_USEC, .msg_last_ut = retention.last_event.created_ns / NSEC_PER_USEC, .size = retention.size_bytes, .source_type = sources, .source = string_strdupz(fullname), }; if(strncmp(fullname, "Netdata", 7) == 0) // WEL based providers of Netdata are named NetdataX provider = "Netdata"; if(provider && *provider) { char buf[sizeof(WEVT_SOURCE_ALL_OF_PROVIDER_PREFIX) + strlen(provider)]; // sizeof() includes terminator snprintf(buf, sizeof(buf), WEVT_SOURCE_ALL_OF_PROVIDER_PREFIX "%s", provider); if(trim_all(buf) != NULL) { for (size_t i = 0; i < sizeof(buf) - 1; i++) { // remove character that may interfere with our parsing if (isspace((uint8_t) buf[i]) || buf[i] == '%' || buf[i] == '+' || buf[i] == '|' || buf[i] == ':') buf[i] = '_'; } src.provider = string_strdupz(buf); } } dictionary_set(wevt_sources, src.fullname, &src, sizeof(src)); } // // add custom queries // for(size_t i = 0; i < sizeof(custom_queries) / sizeof(custom_queries[0]) ;i++) { // EVT_RETENTION retention; // if(!wevt_channel_retention(log, NULL, custom_queries[i].query, &retention)) // continue; // // LOGS_QUERY_SOURCE src = { // .entries = 0, // .fullname = strdupz(custom_queries[i].name), // .fullname_len = strlen(custom_queries[i].name), // .last_scan_monotonic_ut = now_monotonic_usec(), // .msg_first_id = retention.first_event.id, // .msg_last_id = retention.last_event.id, // .msg_first_ut = retention.first_event.created_ns / NSEC_PER_USEC, // .msg_last_ut = retention.last_event.created_ns / NSEC_PER_USEC, // .size = retention.size_bytes, // .source_type = WEVTS_ALL, // .source = string_strdupz(custom_queries[i].name), // }; // // dictionary_set(wevt_sources, src.fullname, &src, sizeof(src)); // } // wevt_closelog6(log); LOGS_QUERY_SOURCE *src; dfe_start_write(wevt_sources, src) { if(src->last_scan_monotonic_ut < started_ut) { src->msg_first_id = 0; src->msg_last_id = 0; src->msg_first_ut = 0; src->msg_last_ut = 0; src->size = 0; dictionary_del(wevt_sources, src->fullname); } } dfe_done(src); dictionary_garbage_collect(wevt_sources); spinlock_unlock(&spinlock); wevt_variant_cleanup(&property); } cleanup: freez(channel); EvtClose(hChannelEnum); }