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