diff options
Diffstat (limited to 'fluent-bit/plugins/in_winevtlog')
-rw-r--r-- | fluent-bit/plugins/in_winevtlog/CMakeLists.txt | 6 | ||||
-rw-r--r-- | fluent-bit/plugins/in_winevtlog/in_winevtlog.c | 279 | ||||
-rw-r--r-- | fluent-bit/plugins/in_winevtlog/pack.c | 625 | ||||
-rw-r--r-- | fluent-bit/plugins/in_winevtlog/winevtlog.c | 840 | ||||
-rw-r--r-- | fluent-bit/plugins/in_winevtlog/winevtlog.h | 134 |
5 files changed, 1884 insertions, 0 deletions
diff --git a/fluent-bit/plugins/in_winevtlog/CMakeLists.txt b/fluent-bit/plugins/in_winevtlog/CMakeLists.txt new file mode 100644 index 000000000..8f4c0e233 --- /dev/null +++ b/fluent-bit/plugins/in_winevtlog/CMakeLists.txt @@ -0,0 +1,6 @@ +set(src + in_winevtlog.c + pack.c + winevtlog.c) + +FLB_PLUGIN(in_winevtlog "${src}" "wevtapi") diff --git a/fluent-bit/plugins/in_winevtlog/in_winevtlog.c b/fluent-bit/plugins/in_winevtlog/in_winevtlog.c new file mode 100644 index 000000000..60c5bcecb --- /dev/null +++ b/fluent-bit/plugins/in_winevtlog/in_winevtlog.c @@ -0,0 +1,279 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2019-2021 The Fluent Bit Authors + * Copyright (C) 2015-2018 Treasure Data Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fluent-bit/flb_compat.h> +#include <fluent-bit/flb_input_plugin.h> +#include <fluent-bit/flb_kernel.h> +#include <fluent-bit/flb_pack.h> +#include <fluent-bit/flb_utils.h> +#include <fluent-bit/flb_sqldb.h> +#include "winevtlog.h" + +#define DEFAULT_INTERVAL_SEC 1 +#define DEFAULT_INTERVAL_NSEC 0 +#define DEFAULT_THRESHOLD_SIZE 0x7ffff /* Default reading buffer size (512kb) */ + +static int in_winevtlog_collect(struct flb_input_instance *ins, + struct flb_config *config, void *in_context); + +static int in_winevtlog_init(struct flb_input_instance *in, + struct flb_config *config, void *data) +{ + int ret; + const char *tmp; + int read_existing_events = FLB_FALSE; + struct mk_list *head; + struct winevtlog_channel *ch; + struct winevtlog_config *ctx; + + /* Initialize context */ + ctx = flb_calloc(1, sizeof(struct winevtlog_config)); + if (ctx == NULL) { + flb_errno(); + return -1; + } + ctx->ins = in; + + ctx->log_encoder = flb_log_event_encoder_create(FLB_LOG_EVENT_FORMAT_DEFAULT); + + if (ctx->log_encoder == NULL) { + flb_plg_error(in, "could not initialize event encoder"); + flb_free(ctx); + + return NULL; + } + + /* Load the config map */ + ret = flb_input_config_map_set(in, (void *) ctx); + if (ret == -1) { + flb_log_event_encoder_destroy(ctx->log_encoder); + flb_free(ctx); + return -1; + } + + /* Set up total reading size threshold */ + ctx->total_size_threshold = DEFAULT_THRESHOLD_SIZE; + + /* Open channels */ + tmp = flb_input_get_property("channels", in); + if (!tmp) { + flb_plg_debug(ctx->ins, "no channel provided. listening to 'Application'"); + tmp = "Application"; + } + + ctx->active_channel = winevtlog_open_all(tmp, ctx); + if (!ctx->active_channel) { + flb_plg_error(ctx->ins, "failed to open channels"); + flb_log_event_encoder_destroy(ctx->log_encoder); + flb_free(ctx); + return -1; + } + + /* Initialize SQLite DB (optional) */ + tmp = flb_input_get_property("db", in); + if (tmp) { + ctx->db = flb_sqldb_open(tmp, in->name, config); + if (!ctx->db) { + flb_plg_error(ctx->ins, "could not open/create database"); + winevtlog_close_all(ctx->active_channel); + flb_log_event_encoder_destroy(ctx->log_encoder); + flb_free(ctx); + return -1; + } + + ret = flb_sqldb_query(ctx->db, SQL_CREATE_CHANNELS, NULL, NULL); + if (ret != FLB_OK) { + flb_plg_error(ctx->ins, "could not create 'channels' table"); + flb_sqldb_close(ctx->db); + winevtlog_close_all(ctx->active_channel); + flb_log_event_encoder_destroy(ctx->log_encoder); + flb_free(ctx); + return -1; + } + + mk_list_foreach(head, ctx->active_channel) { + ch = mk_list_entry(head, struct winevtlog_channel, _head); + winevtlog_sqlite_load(ch, ctx->db); + flb_plg_debug(ctx->ins, "load channel<%s time=%u>", + ch->name, ch->time_created); + } + } + + /* Set the context */ + flb_input_set_context(in, ctx); + + /* Set the collector */ + ret = flb_input_set_collector_time(in, + in_winevtlog_collect, + ctx->interval_sec, + ctx->interval_nsec, + config); + if (ret == -1) { + flb_plg_error(ctx->ins, "could not set up a collector"); + } + ctx->coll_fd = ret; + + return 0; +} + +static int in_winevtlog_read_channel(struct flb_input_instance *ins, + struct winevtlog_config *ctx, + struct winevtlog_channel *ch) +{ + unsigned int read; + + if (winevtlog_read(ch, ctx, &read)) { + flb_plg_error(ctx->ins, "failed to read '%s'", ch->name); + return -1; + } + if (read == 0) { + return 0; + } + flb_plg_debug(ctx->ins, "read %u bytes from '%s'", read, ch->name); + + if (ctx->db) { + ch->time_updated = time(NULL); + flb_plg_debug(ctx->ins, "save channel<%s time=%u>", + ch->name, ch->time_updated); + winevtlog_sqlite_save(ch, ctx->db); + } + + if (ctx->log_encoder->output_length > 0) { + flb_input_log_append(ctx->ins, NULL, 0, + ctx->log_encoder->output_buffer, + ctx->log_encoder->output_length); + } + + flb_log_event_encoder_reset(ctx->log_encoder); + + return 0; +} + +static int in_winevtlog_collect(struct flb_input_instance *ins, + struct flb_config *config, void *in_context) +{ + struct winevtlog_config *ctx = in_context; + struct mk_list *head; + struct winevtlog_channel *ch; + + mk_list_foreach(head, ctx->active_channel) { + ch = mk_list_entry(head, struct winevtlog_channel, _head); + in_winevtlog_read_channel(ins, ctx, ch); + } + return 0; +} + +static void in_winevtlog_pause(void *data, struct flb_config *config) +{ + struct winevtlog_config *ctx = data; + flb_input_collector_pause(ctx->coll_fd, ctx->ins); +} + +static void in_winevtlog_resume(void *data, struct flb_config *config) +{ + struct winevtlog_config *ctx = data; + flb_input_collector_resume(ctx->coll_fd, ctx->ins); +} + +static int in_winevtlog_exit(void *data, struct flb_config *config) +{ + struct winevtlog_config *ctx = data; + + if (!ctx) { + return 0; + } + + winevtlog_close_all(ctx->active_channel); + + if (ctx->db) { + flb_sqldb_close(ctx->db); + } + flb_free(ctx); + + return 0; +} + +static struct flb_config_map config_map[] = { + { + FLB_CONFIG_MAP_STR, "channels", NULL, + 0, FLB_FALSE, 0, + "Specify a comma-separated list of channels to read from" + }, + { + FLB_CONFIG_MAP_STR, "db", NULL, + 0, FLB_FALSE, 0, + "Specify DB file to save read offsets" + }, + { + FLB_CONFIG_MAP_TIME, "interval_sec", "1s", + 0, FLB_TRUE, offsetof(struct winevtlog_config, interval_sec), + "Set the polling interval for each channel" + }, + { + FLB_CONFIG_MAP_INT, "interval_nsec", "0", + 0, FLB_TRUE, offsetof(struct winevtlog_config, interval_nsec), + "Set the polling interval for each channel (sub seconds)" + }, + { + FLB_CONFIG_MAP_BOOL, "string_inserts", "true", + 0, FLB_TRUE, offsetof(struct winevtlog_config, string_inserts), + "Whether to include StringInserts in output records" + }, + { + FLB_CONFIG_MAP_BOOL, "read_existing_events", "false", + 0, FLB_TRUE, offsetof(struct winevtlog_config, read_existing_events), + "Whether to consume at oldest records in channels" + }, + { + FLB_CONFIG_MAP_BOOL, "render_event_as_xml", "false", + 0, FLB_TRUE, offsetof(struct winevtlog_config, render_event_as_xml), + "Whether to consume at oldest records in channels" + }, + { + FLB_CONFIG_MAP_BOOL, "use_ansi", "false", + 0, FLB_TRUE, offsetof(struct winevtlog_config, use_ansi), + "Use ANSI encoding on eventlog messages" + }, + { + FLB_CONFIG_MAP_BOOL, "ignore_missing_channels", "false", + 0, FLB_TRUE, offsetof(struct winevtlog_config, ignore_missing_channels), + "Whether to ignore channels missing in eventlog" + }, + { + FLB_CONFIG_MAP_STR, "event_query", "*", + 0, FLB_TRUE, offsetof(struct winevtlog_config, event_query), + "Specify XML query for filtering events" + }, + /* EOF */ + {0} +}; + +struct flb_input_plugin in_winevtlog_plugin = { + .name = "winevtlog", + .description = "Windows EventLog using winevt.h API", + .cb_init = in_winevtlog_init, + .cb_pre_run = NULL, + .cb_collect = in_winevtlog_collect, + .cb_flush_buf = NULL, + .cb_pause = in_winevtlog_pause, + .cb_resume = in_winevtlog_resume, + .cb_exit = in_winevtlog_exit, + .config_map = config_map +}; diff --git a/fluent-bit/plugins/in_winevtlog/pack.c b/fluent-bit/plugins/in_winevtlog/pack.c new file mode 100644 index 000000000..28075436b --- /dev/null +++ b/fluent-bit/plugins/in_winevtlog/pack.c @@ -0,0 +1,625 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2019-2021 The Fluent Bit Authors + * Copyright (C) 2015-2018 Treasure Data Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fluent-bit/flb_compat.h> +#include <fluent-bit/flb_mem.h> +#include <fluent-bit/flb_utils.h> +#include <fluent-bit/flb_pack.h> +#include <fluent-bit/flb_input_plugin.h> +#include <msgpack.h> +#include <sddl.h> +#include <locale.h> +#include "winevtlog.h" + +#define FORMAT_ISO8601 "%Y-%m-%d %H:%M:%S %z" + +#define BINDATA(evt) ((unsigned char *) (evt) + (evt)->DataOffset) + +static int pack_nullstr(struct winevtlog_config *ctx) +{ + flb_log_event_encoder_append_body_cstring(ctx->log_encoder, ""); +} + +static int pack_wstr(struct winevtlog_config *ctx, const wchar_t *wstr) +{ + int size; + char *buf; + UINT code_page = CP_UTF8; + LPCSTR defaultChar = L" "; + + if (ctx->use_ansi) { + code_page = CP_ACP; + } + + /* Compute the buffer size first */ + size = WideCharToMultiByte(code_page, 0, wstr, -1, NULL, 0, NULL, NULL); + if (size == 0) { + return -1; + } + + buf = flb_malloc(size); + if (buf == NULL) { + flb_errno(); + return -1; + } + + /* Convert UTF-16 into UTF-8 */ + size = WideCharToMultiByte(code_page, 0, wstr, -1, buf, size, defaultChar, NULL); + if (size == 0) { + flb_free(buf); + return -1; + } + + /* Pack buf except the trailing '\0' */ + flb_log_event_encoder_append_body_string(ctx->log_encoder, buf, size - 1); + + flb_free(buf); + return 0; +} + +static int pack_binary(struct winevtlog_config *ctx, PBYTE bin, size_t length) +{ + const char *HEX_TABLE = "0123456789ABCDEF"; + char *buffer; + int size = length * 2; + size_t i, j; + unsigned int idx = 0; + + if (length == 0) { + pack_nullstr(ctx->log_encoder); + return 0; + } + + buffer = flb_malloc(size); + if (buffer == NULL) { + flb_errno(); + return -1; + } + + for (i = 0; i < length; i++) { + for (j = 0; j < 2; j++) { + idx = (unsigned int)(bin[i] >> (j * 4) & 0x0F); + buffer[2*i+(1-j)] = HEX_TABLE[idx]; + } + } + + flb_log_event_encoder_append_body_string(ctx->log_encoder, buffer, size); + + flb_free(buffer); + + return 0; +} + +static int pack_guid(struct winevtlog_config *ctx, const GUID *guid) +{ + LPOLESTR p = NULL; + + if (FAILED(StringFromCLSID(guid, &p))) { + return -1; + } + if (pack_wstr(ctx, p)) { + CoTaskMemFree(p); + return -1; + } + + CoTaskMemFree(p); + + return 0; +} + +static int pack_hex32(struct winevtlog_config *ctx, int32_t hex) +{ + CHAR buffer[32]; + size_t size = _countof(buffer); + + _snprintf_s(buffer, + size, + _TRUNCATE, + "0x%lx", + hex); + size = strlen(buffer); + if (size > 0) { + flb_log_event_encoder_append_body_cstring(ctx->log_encoder, buffer); + + return 0; + } + + return -1; +} + +static int pack_hex64(struct winevtlog_config *ctx, int64_t hex) +{ + CHAR buffer[32]; + size_t size = _countof(buffer); + + _snprintf_s(buffer, + size, + _TRUNCATE, + "0x%llx", + hex); + + size = strlen(buffer); + if (size > 0) { + flb_log_event_encoder_append_body_cstring(ctx->log_encoder, buffer); + + return 0; + } + + return -1; +} + + +static int pack_keywords(struct winevtlog_config *ctx, uint64_t keywords) +{ + CHAR buffer[32]; + size_t size = _countof(buffer); + + _snprintf_s(buffer, + size, + _TRUNCATE, + "0x%llx", + keywords); + + size = strlen(buffer); + + flb_log_event_encoder_append_body_cstring(ctx->log_encoder, buffer); + + return 0; +} + +static int pack_systemtime(struct winevtlog_config *ctx, SYSTEMTIME *st) +{ + CHAR buf[64]; + size_t len = 0; + _locale_t locale; + TIME_ZONE_INFORMATION tzi; + SYSTEMTIME st_local; + + GetTimeZoneInformation(&tzi); + + locale = _get_current_locale(); + if (locale == NULL) { + return -1; + } + if (st != NULL) { + SystemTimeToTzSpecificLocalTime(&tzi, st, &st_local); + + struct tm tm = {st_local.wSecond, + st_local.wMinute, + st_local.wHour, + st_local.wDay, + st_local.wMonth-1, + st_local.wYear-1900, + st_local.wDayOfWeek, 0, 0}; + len = _strftime_l(buf, 64, FORMAT_ISO8601, &tm, locale); + if (len == 0) { + flb_errno(); + _free_locale(locale); + return -1; + } + _free_locale(locale); + + flb_log_event_encoder_append_body_string(ctx->log_encoder, buf, len); + } + else { + return -1; + } + + return 0; +} + +static int pack_filetime(struct winevtlog_config *ctx, ULONGLONG filetime) +{ + LARGE_INTEGER timestamp; + CHAR buf[64]; + size_t len = 0; + FILETIME ft, ft_local; + SYSTEMTIME st; + _locale_t locale; + + locale = _get_current_locale(); + if (locale == NULL) { + return -1; + } + timestamp.QuadPart = filetime; + ft.dwHighDateTime = timestamp.HighPart; + ft.dwLowDateTime = timestamp.LowPart; + FileTimeToLocalFileTime(&ft, &ft_local); + if (FileTimeToSystemTime(&ft_local, &st)) { + struct tm tm = {st.wSecond, st.wMinute, st.wHour, st.wDay, st.wMonth-1, st.wYear-1900, st.wDayOfWeek, 0, 0}; + len = _strftime_l(buf, 64, FORMAT_ISO8601, &tm, locale); + if (len == 0) { + flb_errno(); + _free_locale(locale); + return -1; + } + _free_locale(locale); + + flb_log_event_encoder_append_body_string(ctx->log_encoder, buf, len); + } + else { + return -1; + } + + return 0; +} + +static int pack_sid(struct winevtlog_config *ctx, PSID sid) +{ + size_t size; + LPWSTR wide_sid = NULL; + int ret = -1; + + if (ConvertSidToStringSidW(sid, &wide_sid)) { + ret = pack_wstr(ctx, wide_sid); + + LocalFree(wide_sid); + return ret; + } + + return ret; +} + +static void pack_string_inserts(struct winevtlog_config *ctx, PEVT_VARIANT values, DWORD count) +{ + int i; + int ret; + + ret = flb_log_event_encoder_body_begin_array(ctx->log_encoder); + + for (i = 0; i < count; i++) { + if (values[i].Type & EVT_VARIANT_TYPE_ARRAY) { + continue; + } + + switch (values[i].Type & EVT_VARIANT_TYPE_MASK) { + case EvtVarTypeNull: + pack_nullstr(ctx); + break; + case EvtVarTypeString: + if (pack_wstr(ctx, values[i].StringVal)) { + pack_nullstr(ctx); + } + break; + case EvtVarTypeAnsiString: + if (pack_wstr(ctx, values[i].AnsiStringVal)) { + pack_nullstr(ctx); + } + break; + case EvtVarTypeSByte: + flb_log_event_encoder_append_body_int8(ctx->log_encoder, values[i].SByteVal); + break; + case EvtVarTypeByte: + flb_log_event_encoder_append_body_uint8(ctx->log_encoder, values[i].ByteVal); + break; + case EvtVarTypeInt16: + flb_log_event_encoder_append_body_int16(ctx->log_encoder, values[i].Int16Val); + break; + case EvtVarTypeUInt16: + flb_log_event_encoder_append_body_uint16(ctx->log_encoder, values[i].UInt16Val); + break; + case EvtVarTypeInt32: + flb_log_event_encoder_append_body_int32(ctx->log_encoder, values[i].Int32Val); + break; + case EvtVarTypeUInt32: + flb_log_event_encoder_append_body_uint32(ctx->log_encoder, values[i].UInt32Val); + break; + case EvtVarTypeInt64: + flb_log_event_encoder_append_body_int64(ctx->log_encoder, values[i].Int64Val); + break; + case EvtVarTypeUInt64: + flb_log_event_encoder_append_body_uint64(ctx->log_encoder, values[i].UInt64Val); + break; + case EvtVarTypeSingle: + flb_log_event_encoder_append_body_double(ctx->log_encoder, values[i].SingleVal); + break; + case EvtVarTypeDouble: + flb_log_event_encoder_append_body_double(ctx->log_encoder, values[i].DoubleVal); + break; + case EvtVarTypeBoolean: + flb_log_event_encoder_append_body_boolean(ctx->log_encoder, (int) values[i].BooleanVal); + break; + case EvtVarTypeGuid: + if (pack_guid(ctx, values[i].GuidVal)) { + pack_nullstr(ctx); + } + break; + case EvtVarTypeSizeT: + flb_log_event_encoder_append_body_uint64(ctx->log_encoder, values[i].SizeTVal); + break; + case EvtVarTypeFileTime: + if (pack_filetime(ctx, values[i].FileTimeVal)) { + pack_nullstr(ctx); + } + break; + case EvtVarTypeSysTime: + if (pack_systemtime(ctx, values[i].SysTimeVal)) { + pack_nullstr(ctx); + } + break; + case EvtVarTypeSid: + if (pack_sid(ctx, values[i].SidVal)) { + pack_nullstr(ctx); + } + break; + case EvtVarTypeHexInt32: + if (pack_hex32(ctx, values[i].Int32Val)) { + pack_nullstr(ctx); + } + break; + case EvtVarTypeHexInt64: + if (pack_hex64(ctx, values[i].Int64Val)) { + pack_nullstr(ctx); + } + break; + case EvtVarTypeEvtXml: + if (pack_wstr(ctx, values[i].XmlVal, ctx)) { + pack_nullstr(ctx); + } + break; + case EvtVarTypeBinary: + if (pack_binary(ctx, values[i].BinaryVal, values[i].Count)) { + pack_nullstr(ctx); + } + break; + default: + flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "?"); + } + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_body_commit_array(ctx->log_encoder); + } + +} + +void winevtlog_pack_xml_event(WCHAR *system_xml, WCHAR *message, + PEVT_VARIANT string_inserts, UINT count_inserts, struct winevtlog_channel *ch, + struct winevtlog_config *ctx) +{ + int ret; + + ret = flb_log_event_encoder_begin_record(ctx->log_encoder); + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_set_current_timestamp(ctx->log_encoder); + } + + + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "System"); + + if (pack_wstr(ctx, system_xml)) { + pack_nullstr(ctx); + } + + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "Message"); + + if (pack_wstr(ctx, message)) { + pack_nullstr(ctx); + } + + if (ctx->string_inserts) { + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "StringInserts"); + + pack_string_inserts(ctx, string_inserts, count_inserts); + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_commit_record(ctx->log_encoder); + } +} + +void winevtlog_pack_event(PEVT_VARIANT system, WCHAR *message, + PEVT_VARIANT string_inserts, UINT count_inserts, struct winevtlog_channel *ch, + struct winevtlog_config *ctx) +{ + int ret; + size_t len; + int count = 19; + + if (ctx->string_inserts) { + count++; + } + + ret = flb_log_event_encoder_begin_record(ctx->log_encoder); + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_set_current_timestamp(ctx->log_encoder); + } + + /* ProviderName */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "ProviderName"); + + if (pack_wstr(ctx, system[EvtSystemProviderName].StringVal)) { + pack_nullstr(ctx); + } + + /* ProviderGuid */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "ProviderGuid"); + + if (EvtVarTypeNull != system[EvtSystemProviderGuid].Type) { + if (pack_guid(ctx, system[EvtSystemProviderGuid].GuidVal)) { + pack_nullstr(ctx); + } + } + else { + pack_nullstr(ctx); + } + + /* Qualifiers */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "Qualifiers"); + + if (EvtVarTypeNull != system[EvtSystemQualifiers].Type) { + flb_log_event_encoder_append_body_uint16(ctx->log_encoder, system[EvtSystemQualifiers].UInt16Val); + } + else { + pack_nullstr(ctx); + } + + /* EventID */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "EventID"); + + if (EvtVarTypeNull != system[EvtSystemEventID].Type) { + flb_log_event_encoder_append_body_uint16(ctx->log_encoder, system[EvtSystemEventID].UInt16Val); + } + else { + pack_nullstr(ctx); + } + + /* Version */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "Version"); + + if (EvtVarTypeNull != system[EvtSystemVersion].Type) { + flb_log_event_encoder_append_body_uint8(ctx->log_encoder, system[EvtSystemVersion].ByteVal); + } + else { + flb_log_event_encoder_append_body_uint8(ctx->log_encoder, 0); + } + + /* Level */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "Level"); + + if (EvtVarTypeNull != system[EvtSystemLevel].Type) { + flb_log_event_encoder_append_body_uint8(ctx->log_encoder, system[EvtSystemLevel].ByteVal); + } + else { + flb_log_event_encoder_append_body_uint8(ctx->log_encoder, 0); + } + + /* Task */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "Task"); + + if (EvtVarTypeNull != system[EvtSystemTask].Type) { + flb_log_event_encoder_append_body_uint16(ctx->log_encoder, system[EvtSystemTask].UInt16Val); + } + else { + flb_log_event_encoder_append_body_uint16(ctx->log_encoder, 0); + } + + /* Opcode */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "Opcode"); + + if (EvtVarTypeNull != system[EvtSystemOpcode].Type) { + flb_log_event_encoder_append_body_uint8(ctx->log_encoder, system[EvtSystemOpcode].ByteVal); + } + else { + flb_log_event_encoder_append_body_uint8(ctx->log_encoder, 0); + } + + /* Keywords */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "Keywords"); + + if (EvtVarTypeNull != system[EvtSystemKeywords].Type) { + pack_keywords(ctx, system[EvtSystemKeywords].UInt64Val); + } + else { + flb_log_event_encoder_append_body_uint64(ctx->log_encoder, 0); + } + + /* TimeCreated */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "TimeCreated"); + + if (pack_filetime(ctx, system[EvtSystemTimeCreated].FileTimeVal)) { + pack_nullstr(ctx); + } + + /* EventRecordID */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "EventRecordID"); + + if (EvtVarTypeNull != system[EvtSystemEventRecordId].Type) { + flb_log_event_encoder_append_body_uint64(ctx->log_encoder, system[EvtSystemEventRecordId].UInt64Val); + } + else { + flb_log_event_encoder_append_body_uint64(ctx->log_encoder, 0); + } + + /* ActivityID */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "ActivityID"); + + if (pack_guid(ctx, system[EvtSystemActivityID].GuidVal)) { + pack_nullstr(ctx); + } + + /* Related ActivityID */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "RelatedActivityID"); + + if (pack_guid(ctx, system[EvtSystemRelatedActivityID].GuidVal)) { + pack_nullstr(ctx); + } + + /* ProcessID */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "ProcessID"); + + if (EvtVarTypeNull != system[EvtSystemProcessID].Type) { + flb_log_event_encoder_append_body_uint32(ctx->log_encoder, system[EvtSystemProcessID].UInt32Val); + } + else { + flb_log_event_encoder_append_body_uint32(ctx->log_encoder, 0); + } + + /* ThreadID */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "ThreadID"); + + if (EvtVarTypeNull != system[EvtSystemThreadID].Type) { + flb_log_event_encoder_append_body_uint32(ctx->log_encoder, system[EvtSystemThreadID].UInt32Val); + } + else { + flb_log_event_encoder_append_body_uint32(ctx->log_encoder, 0); + } + + /* Channel */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "Channel"); + + if (pack_wstr(ctx, system[EvtSystemChannel].StringVal)) { + pack_nullstr(ctx); + } + + /* Computer */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "Computer"); + + if (pack_wstr(ctx, system[EvtSystemComputer].StringVal)) { + pack_nullstr(ctx); + } + + /* UserID */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "UserID"); + + if (pack_sid(ctx, system[EvtSystemUserID].SidVal)) { + pack_nullstr(ctx); + } + + /* Message */ + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "Message"); + + if (pack_wstr(ctx, message)) { + pack_nullstr(ctx); + } + + /* String Inserts */ + if (ctx->string_inserts) { + ret = flb_log_event_encoder_append_body_cstring(ctx->log_encoder, "StringInserts"); + + pack_string_inserts(ctx, string_inserts, count_inserts); + } + + if (ret == FLB_EVENT_ENCODER_SUCCESS) { + ret = flb_log_event_encoder_commit_record(ctx->log_encoder); + } +} diff --git a/fluent-bit/plugins/in_winevtlog/winevtlog.c b/fluent-bit/plugins/in_winevtlog/winevtlog.c new file mode 100644 index 000000000..09d3f9624 --- /dev/null +++ b/fluent-bit/plugins/in_winevtlog/winevtlog.c @@ -0,0 +1,840 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2019-2021 The Fluent Bit Authors + * Copyright (C) 2015-2018 Treasure Data Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fluent-bit/flb_compat.h> +#include <fluent-bit/flb_info.h> +#include <fluent-bit/flb_utils.h> +#include <fluent-bit/flb_sqldb.h> +#include <fluent-bit/flb_input.h> +#include "winevtlog.h" + +#define EVENT_PROVIDER_NAME_LENGTH 256 + +static char* convert_wstr(wchar_t *wstr, UINT codePage); +static wchar_t* convert_str(char *str); + +struct winevtlog_channel *winevtlog_subscribe(const char *channel, int read_existing_events, + EVT_HANDLE stored_bookmark, const char *query) +{ + struct winevtlog_channel *ch; + EVT_HANDLE bookmark = NULL; + HANDLE signal_event = NULL; + DWORD len; + DWORD flags = 0L; + PWSTR wide_channel = NULL; + PWSTR wide_query = NULL; + void *buf; + + ch = flb_calloc(1, sizeof(struct winevtlog_channel)); + if (ch == NULL) { + flb_errno(); + return NULL; + } + + ch->name = flb_strdup(channel); + if (!ch->name) { + flb_errno(); + flb_free(ch); + return NULL; + } + ch->query = NULL; + + signal_event = CreateEvent(NULL, FALSE, FALSE, NULL); + + // channel : To wide char + len = MultiByteToWideChar(CP_UTF8, 0, channel, -1, NULL, 0); + wide_channel = flb_malloc(sizeof(PWSTR) * len); + MultiByteToWideChar(CP_UTF8, 0, channel, -1, wide_channel, len); + if (query != NULL) { + // query : To wide char + len = MultiByteToWideChar(CP_UTF8, 0, query, -1, NULL, 0); + wide_query = flb_malloc(sizeof(PWSTR) * len); + MultiByteToWideChar(CP_UTF8, 0, query, -1, wide_query, len); + ch->query = flb_strdup(query); + } + + if (stored_bookmark) { + flags |= EvtSubscribeStartAfterBookmark; + } else if (read_existing_events) { + flags |= EvtSubscribeStartAtOldestRecord; + } else { + flags |= EvtSubscribeToFutureEvents; + } + + /* The wide_query parameter can handle NULL as `*` for retrieving all events. + * ref. https://learn.microsoft.com/en-us/windows/win32/api/winevt/nf-winevt-evtsubscribe + */ + ch->subscription = EvtSubscribe(NULL, signal_event, wide_channel, wide_query, + stored_bookmark, NULL, NULL, flags); + if (!ch->subscription) { + flb_error("[in_winevtlog] cannot subscribe '%s' (%i)", channel, GetLastError()); + flb_free(ch->name); + if (ch->query != NULL) { + flb_free(ch->query); + } + flb_free(ch); + return NULL; + } + ch->signal_event = signal_event; + + if (stored_bookmark) { + ch->bookmark = stored_bookmark; + } + else { + bookmark = EvtCreateBookmark(NULL); + if (bookmark) { + ch->bookmark = bookmark; + } + else { + if (ch->subscription) { + EvtClose(ch->subscription); + } + if (signal_event) { + CloseHandle(signal_event); + } + flb_error("[in_winevtlog] cannot subscribe '%s' (%i)", channel, GetLastError()); + flb_free(wide_channel); + flb_free(ch->name); + if (ch->query != NULL) { + flb_free(ch->query); + } + flb_free(ch); + return NULL; + } + } + + flb_free(wide_channel); + if (wide_query != NULL) { + flb_free(wide_query); + } + + return ch; +} + +BOOL cancel_subscription(struct winevtlog_channel *ch) +{ + return EvtCancel(ch->subscription); +} + +static void close_handles(struct winevtlog_channel *ch) +{ + int i; + + if (ch->subscription) { + EvtClose(ch->subscription); + ch->subscription = NULL; + } + if (ch->signal_event) { + CloseHandle(ch->signal_event); + ch->signal_event = NULL; + } + if (ch->bookmark) { + EvtClose(ch->bookmark); + ch->bookmark = NULL; + } + for (i = 0; i < ch->count; i++) { + if (ch->events[i]) { + EvtClose(ch->events[i]); + ch->events[i] = NULL; + } + } + ch->count = 0; +} + + +void winevtlog_close(struct winevtlog_channel *ch) +{ + flb_free(ch->name); + if (ch->query != NULL) { + flb_free(ch->query); + } + close_handles(ch); + + flb_free(ch); +} + +// Render the event as an XML string and print it. +PWSTR render_event(EVT_HANDLE hEvent, DWORD flags, unsigned int *event_size) +{ + DWORD status = ERROR_SUCCESS; + DWORD buffer_size = 0; + DWORD buffer_used = 0; + DWORD count = 0; + LPWSTR event_xml = NULL; + + if (flags != EvtRenderEventXml && flags != EvtRenderBookmark) { + flb_error("Invalid flags is specified: %d", flags); + return NULL; + } + + if (!EvtRender(NULL, hEvent, flags, buffer_size, event_xml, &buffer_used, &count)) { + status = GetLastError(); + if (status == ERROR_INSUFFICIENT_BUFFER) { + buffer_size = buffer_used; + /* return buffer size */ + *event_size = buffer_size; + event_xml = (LPWSTR)flb_malloc(buffer_size); + if (event_xml) { + EvtRender(NULL, hEvent, flags, buffer_size, event_xml, &buffer_used, &count); + } + else { + flb_error("malloc failed"); + goto cleanup; + } + } + + status = GetLastError(); + if (status != ERROR_SUCCESS) { + flb_error("EvtRender failed with %d", GetLastError()); + goto cleanup; + } + } + + return event_xml; + +cleanup: + + if (event_xml) { + flb_free(event_xml); + } + + return NULL; +} + +DWORD render_system_event(EVT_HANDLE event, PEVT_VARIANT *system, unsigned int *system_size) +{ + DWORD status = ERROR_SUCCESS; + EVT_HANDLE context = NULL; + DWORD buffer_size = 0; + DWORD buffer_used = 0; + DWORD count = 0; + PEVT_VARIANT rendered_system = NULL; + + context = EvtCreateRenderContext(0, NULL, EvtRenderContextSystem); + if (NULL == context) { + status = GetLastError(); + flb_error("failed to create RenderContext with %d", status); + + goto cleanup; + } + if (!EvtRender(context, + event, + EvtRenderEventValues, + buffer_size, + rendered_system, + &buffer_used, + &count)) { + status = GetLastError(); + + if (status == ERROR_INSUFFICIENT_BUFFER) { + buffer_size = buffer_used; + rendered_system = (PEVT_VARIANT)flb_malloc(buffer_size); + if (rendered_system) { + EvtRender(context, + event, + EvtRenderEventValues, + buffer_size, + rendered_system, + &buffer_used, + &count); + status = GetLastError(); + *system_size = buffer_used; + } else { + if (rendered_system) + flb_free(rendered_system); + + flb_error("failed to malloc memory with %d", status); + + goto cleanup; + } + } + + if (ERROR_SUCCESS != status) { + EvtClose(context); + flb_free(rendered_system); + + return status; + } + } + + *system = rendered_system; + +cleanup: + + if (context) { + EvtClose(context); + } + + return status; +} + + +PWSTR get_message(EVT_HANDLE metadata, EVT_HANDLE handle, unsigned int *message_size) +{ + WCHAR* buffer = NULL; + DWORD status = ERROR_SUCCESS; + DWORD buffer_size = 0; + DWORD buffer_used = 0; + LPVOID format_message_buffer; + WCHAR* message = NULL; + char *error_message = NULL; + + // Get the size of the buffer + if (!EvtFormatMessage(metadata, handle, 0, 0, NULL, + EvtFormatMessageEvent, buffer_size, buffer, &buffer_used)) { + status = GetLastError(); + if (ERROR_INSUFFICIENT_BUFFER == status) { + buffer_size = buffer_used; + buffer = flb_malloc(sizeof(WCHAR) * buffer_size); + if (!buffer) { + flb_error("failed to malloc message buffer"); + + goto cleanup; + } + if (!EvtFormatMessage(metadata, + handle, + 0xffffffff, + 0, + NULL, + EvtFormatMessageEvent, + buffer_size, + buffer, + &buffer_used)) { + status = GetLastError(); + *message_size = buffer_used; + + if (status != ERROR_EVT_UNRESOLVED_VALUE_INSERT) { + switch (status) { + case ERROR_EVT_MESSAGE_NOT_FOUND: + case ERROR_EVT_MESSAGE_ID_NOT_FOUND: + case ERROR_EVT_MESSAGE_LOCALE_NOT_FOUND: + case ERROR_RESOURCE_DATA_NOT_FOUND: + case ERROR_RESOURCE_TYPE_NOT_FOUND: + case ERROR_RESOURCE_NAME_NOT_FOUND: + case ERROR_RESOURCE_LANG_NOT_FOUND: + case ERROR_MUI_FILE_NOT_FOUND: + case ERROR_EVT_UNRESOLVED_PARAMETER_INSERT: + { + if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + status, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (WCHAR*)(&format_message_buffer), + 0, + NULL) == 0) + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + status, + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + (WCHAR*)(&format_message_buffer), + 0, + NULL); + error_message = convert_wstr((WCHAR*)format_message_buffer, CP_ACP); + flb_error("Failed to get message with %d, err = %s", status, error_message); + flb_free(error_message); + + message = _wcsdup((WCHAR*)format_message_buffer); + LocalFree(format_message_buffer); + + goto cleanup; + } + } + + if (status != ERROR_INSUFFICIENT_BUFFER) { + flb_error("failed with %d", status); + goto cleanup; + } + } + } + } + } + + message = _wcsdup(buffer); + +cleanup: + if (buffer) { + flb_free(buffer); + } + + return message; +} + +PWSTR get_description(EVT_HANDLE handle, LANGID langID, unsigned int *message_size) +{ + WCHAR *buffer[EVENT_PROVIDER_NAME_LENGTH]; + PEVT_VARIANT values = NULL; + DWORD buffer_used = 0; + DWORD status = ERROR_SUCCESS; + DWORD count = 0; + WCHAR *message = NULL; + EVT_HANDLE metadata = NULL; + + PCWSTR properties[] = { L"Event/System/Provider/@Name" }; + EVT_HANDLE context = + EvtCreateRenderContext(1, properties, EvtRenderContextValues); + if (context == NULL) { + flb_error("Failed to create renderContext"); + goto cleanup; + } + + if (EvtRender(context, + handle, + EvtRenderEventValues, + EVENT_PROVIDER_NAME_LENGTH, + buffer, + &buffer_used, + &count) != FALSE){ + status = ERROR_SUCCESS; + } + else { + status = GetLastError(); + } + + if (status != ERROR_SUCCESS) { + flb_error("failed to query RenderContextValues"); + goto cleanup; + } + values = (PEVT_VARIANT)buffer; + + metadata = EvtOpenPublisherMetadata( + NULL, // TODO: Remote handle + values[0].StringVal, + NULL, + MAKELCID(langID, SORT_DEFAULT), + 0); + if (metadata == NULL) { + goto cleanup; + } + + message = get_message(metadata, handle, message_size); + +cleanup: + if (context) { + EvtClose(context); + } + + if (metadata) { + EvtClose(metadata); + } + + return message; +} + +int get_string_inserts(EVT_HANDLE handle, PEVT_VARIANT *string_inserts_values, + UINT *prop_count, unsigned int *string_inserts_size) +{ + PEVT_VARIANT values; + DWORD buffer_size = 0; + DWORD buffer_size_used = 0; + DWORD count = 0; + BOOL succeeded = FLB_TRUE; + + EVT_HANDLE context = EvtCreateRenderContext(0, NULL, EvtRenderContextUser); + if (context == NULL) { + flb_error("Failed to create renderContext"); + succeeded = FLB_FALSE; + goto cleanup; + } + + // Get the size of the buffer + EvtRender(context, handle, EvtRenderEventValues, 0, NULL, &buffer_size, &count); + values = (PEVT_VARIANT)flb_malloc(buffer_size); + + succeeded = EvtRender(context, + handle, + EvtRenderContextValues, + buffer_size, + values, + &buffer_size_used, + &count); + + if (!succeeded) { + flb_error("Failed to get string inserts with %d\n", GetLastError()); + goto cleanup; + } + + *prop_count = count; + *string_inserts_values = values; + *string_inserts_size = buffer_size; + +cleanup: + + if (context != NULL) { + EvtClose(context); + } + + return succeeded; +} + +static int winevtlog_next(struct winevtlog_channel *ch, int hit_threshold) +{ + EVT_HANDLE events[SUBSCRIBE_ARRAY_SIZE]; + DWORD count = 0; + DWORD status = ERROR_SUCCESS; + BOOL has_next = FALSE; + int i; + + /* If subscription handle is NULL, it should return false. */ + if (!ch->subscription) { + flb_error("Invalid subscription is passed"); + return FLB_FALSE; + } + + if (hit_threshold) { + return FLB_FALSE; + } + + has_next = EvtNext(ch->subscription, SUBSCRIBE_ARRAY_SIZE, + events, INFINITE, 0, &count); + + if (!has_next) { + status = GetLastError(); + if (ERROR_CANCELLED == status) { + return FLB_FALSE; + } + if (ERROR_NO_MORE_ITEMS != status) { + return FLB_FALSE; + } + } + + if (status == ERROR_SUCCESS) { + ch->count = count; + for (i = 0; i < count; i++) { + ch->events[i] = events[i]; + EvtUpdateBookmark(ch->bookmark, ch->events[i]); + } + + return FLB_TRUE; + } + + return FLB_FALSE; +} + +/* + * Read from an open Windows Event Log channel. + */ +int winevtlog_read(struct winevtlog_channel *ch, struct winevtlog_config *ctx, + unsigned int *read) +{ + DWORD status = ERROR_SUCCESS; + PWSTR system_xml = NULL; + unsigned int system_size = 0; + unsigned int message_size = 0; + unsigned int string_inserts_size = 0; + int hit_threshold = FLB_FALSE; + unsigned int read_size = 0; + PWSTR message = NULL; + PEVT_VARIANT rendered_system = NULL; + PEVT_VARIANT string_inserts = NULL; + UINT count_inserts = 0; + DWORD i = 0; + + while (winevtlog_next(ch, hit_threshold)) { + for (i = 0; i < ch->count; i++) { + if (ctx->render_event_as_xml) { + system_xml = render_event(ch->events[i], EvtRenderEventXml, &system_size); + message = get_description(ch->events[i], LANG_NEUTRAL, &message_size); + get_string_inserts(ch->events[i], &string_inserts, &count_inserts, &string_inserts_size); + if (system_xml) { + /* Caluculate total allocated size: system + message + string_inserts */ + read_size += (system_size + message_size + string_inserts_size); + winevtlog_pack_xml_event(system_xml, message, string_inserts, + count_inserts, ch, ctx); + + flb_free(string_inserts); + flb_free(system_xml); + if (message) + flb_free(message); + } + } + else { + render_system_event(ch->events[i], &rendered_system, &system_size); + message = get_description(ch->events[i], LANG_NEUTRAL, &message_size); + get_string_inserts(ch->events[i], &string_inserts, &count_inserts, &string_inserts_size); + if (rendered_system) { + /* Caluculate total allocated size: system + message + string_inserts */ + read_size += (system_size + message_size + string_inserts_size); + winevtlog_pack_event(rendered_system, message, string_inserts, + count_inserts, ch, ctx); + + flb_free(string_inserts); + flb_free(rendered_system); + if (message) + flb_free(message); + } + } + } + + /* Closes any events in case an error occurred above. */ + for (i = 0; i < ch->count; i++) { + if (NULL != ch->events[i]) { + EvtClose(ch->events[i]); + ch->events[i] = NULL; + } + } + + if (read_size > ctx->total_size_threshold) { + hit_threshold = FLB_TRUE; + /* hit reading threshold on read, then break. */ + break; + } + } + + *read = read_size; + + return 0; +} + +/* + * Open multiple channels at once. The return value is a linked + * list of winevtlog_channel objects. + * + * "channels" are comma-separated names like "Setup,Security". + */ +struct mk_list *winevtlog_open_all(const char *channels, struct winevtlog_config *ctx) +{ + char *tmp; + char *channel; + char *state; + struct winevtlog_channel *ch; + struct mk_list *list; + + tmp = flb_strdup(channels); + if (!tmp) { + flb_errno(); + return NULL; + } + + list = flb_malloc(sizeof(struct mk_list)); + if (!list) { + flb_errno(); + flb_free(tmp); + return NULL; + } + mk_list_init(list); + + channel = strtok_s(tmp , ",", &state); + while (channel) { + ch = winevtlog_subscribe(channel, ctx->read_existing_events, NULL, ctx->event_query); + if (ch) { + mk_list_add(&ch->_head, list); + } + else { + if (ctx->ignore_missing_channels) { + flb_debug("[in_winevtlog] channel '%s' does not exist", channel); + } + else { + flb_free(tmp); + winevtlog_close_all(list); + return NULL; + } + } + channel = strtok_s(NULL, ",", &state); + } + + if (mk_list_size(list) == 0) { + flb_free(tmp); + winevtlog_close_all(list); + return NULL; + } + + flb_free(tmp); + return list; +} + +void winevtlog_close_all(struct mk_list *list) +{ + struct winevtlog_channel *ch; + struct mk_list *head; + struct mk_list *tmp; + + mk_list_foreach_safe(head, tmp, list) { + ch = mk_list_entry(head, struct winevtlog_channel, _head); + mk_list_del(&ch->_head); + winevtlog_close(ch); + } + flb_free(list); +} + +/* + * Callback function for flb_sqldb_query(). + */ +static int winevtlog_sqlite_callback(void *data, int argc, char **argv, char **cols) +{ + struct winevtlog_sqlite_record *p = data; + + p->name = argv[0]; + p->bookmark_xml = strdup(argv[1]); + p->time_updated = (unsigned int) strtoul(argv[2], NULL, 10); + p->created = (unsigned int) strtoul(argv[3], NULL, 10); + return 0; +} + +static wchar_t* convert_str(char *str) +{ + int size = 0; + wchar_t *buf = NULL; + + size = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + if (size == 0) { + return NULL; + } + + buf = flb_malloc(sizeof(PWSTR) * size); + if (buf == NULL) { + flb_errno(); + return NULL; + } + size = MultiByteToWideChar(CP_UTF8, 0, str, -1, buf, size); + if (size == 0) { + flb_free(buf); + return NULL; + } + + return buf; +} + +static char* convert_wstr(wchar_t *wstr, UINT codePage) +{ + int size = 0; + char *buf = NULL; + + size = WideCharToMultiByte(codePage, 0, wstr, -1, NULL, 0, NULL, NULL); + if (size == 0) { + return NULL; + } + + buf = flb_malloc(size); + if (buf == NULL) { + flb_errno(); + return NULL; + } + size = WideCharToMultiByte(codePage, 0, wstr, -1, buf, size, NULL, NULL); + if (size == 0) { + flb_free(buf); + return NULL; + } + + return buf; +} + +/* + * Load the bookmark from SQLite DB. + */ +int winevtlog_sqlite_load(struct winevtlog_channel *ch, struct flb_sqldb *db) +{ + int ret; + char query[1024]; + struct winevtlog_sqlite_record record = {0}; + EVT_HANDLE bookmark = NULL; + PWSTR bookmark_xml = NULL; + struct winevtlog_channel *re_ch = NULL; + + snprintf(query, sizeof(query) - 1, SQL_GET_CHANNEL, ch->name); + + ret = flb_sqldb_query(db, query, winevtlog_sqlite_callback, &record); + if (ret == FLB_ERROR) { + return -1; + } + + if (record.created) { + ch->time_created = record.created; + } + if (record.time_updated) { + ch->time_updated = record.time_updated; + } + + if (record.name) { + bookmark_xml = convert_str(record.bookmark_xml); + if (bookmark_xml) { + bookmark = EvtCreateBookmark(bookmark_xml); + if (bookmark) { + /* re-create subscription handles */ + re_ch = winevtlog_subscribe(ch->name, FLB_FALSE, bookmark, ch->query); + if (re_ch != NULL) { + close_handles(ch); + + ch->bookmark = re_ch->bookmark; + ch->subscription = re_ch->subscription; + ch->signal_event = re_ch->signal_event; + } + else { + flb_error("Failed to subscribe with bookmark XML: %s\n", record.bookmark_xml); + ch->bookmark = EvtCreateBookmark(NULL); + } + } + else { + flb_error("Failed to load bookmark XML with %d\n", GetLastError()); + ch->bookmark = EvtCreateBookmark(NULL); + } + } + if (bookmark_xml) { + flb_free(bookmark_xml); + } + } + return 0; +} + +/* + * Save the bookmark into SQLite DB. + */ +int winevtlog_sqlite_save(struct winevtlog_channel *ch, struct flb_sqldb *db) +{ + int ret; + char query[1024]; + PWSTR wide_bookmark_xml = NULL; + char *bookmark_xml; + int used_size = 0; + + wide_bookmark_xml = render_event(ch->bookmark, EvtRenderBookmark, &used_size); + if (wide_bookmark_xml == NULL) { + flb_error("failed to render bookmark with %d", GetLastError()); + flb_free(wide_bookmark_xml); + + return -1; + } + bookmark_xml = convert_wstr(wide_bookmark_xml, CP_UTF8); + if (bookmark_xml == NULL) { + flb_error("failed to convert Wider string with %d", GetLastError()); + flb_free(wide_bookmark_xml); + flb_free(bookmark_xml); + + return -1; + } + + snprintf(query, sizeof(query) - 1, SQL_UPDATE_CHANNEL, + ch->name, bookmark_xml, ch->time_updated, time(NULL)); + + ret = flb_sqldb_query(db, query, NULL, NULL); + if (ret == FLB_ERROR) { + flb_error("failed to save db with %d", GetLastError()); + flb_free(wide_bookmark_xml); + flb_free(bookmark_xml); + + return -1; + } + + flb_free(wide_bookmark_xml); + flb_free(bookmark_xml); + + return 0; +} diff --git a/fluent-bit/plugins/in_winevtlog/winevtlog.h b/fluent-bit/plugins/in_winevtlog/winevtlog.h new file mode 100644 index 000000000..10ef3e457 --- /dev/null +++ b/fluent-bit/plugins/in_winevtlog/winevtlog.h @@ -0,0 +1,134 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2019-2021 The Fluent Bit Authors + * Copyright (C) 2015-2018 Treasure Data Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLB_WINEVTLOG_H +#define FLB_WINEVTLOG_H + +#include <winevt.h> +#include <fluent-bit/flb_log_event_encoder.h> + +struct winevtlog_config { + unsigned int interval_sec; + unsigned int interval_nsec; + unsigned int total_size_threshold; + int string_inserts; + int read_existing_events; + int render_event_as_xml; + int use_ansi; + int ignore_missing_channels; + flb_sds_t event_query; + + struct mk_list *active_channel; + struct flb_sqldb *db; + flb_pipefd_t coll_fd; + struct flb_input_instance *ins; + struct flb_log_event_encoder *log_encoder; +}; + +/* Some channels has very heavy contents for 10 events at same time. + * For now, we specify simultaneous subscribe size to 5. + */ +#define SUBSCRIBE_ARRAY_SIZE 5 + +struct winevtlog_channel { + EVT_HANDLE subscription; + EVT_HANDLE bookmark; + HANDLE signal_event; + EVT_HANDLE events[SUBSCRIBE_ARRAY_SIZE]; + int count; + + char *name; + char *query; + unsigned int time_updated; + unsigned int time_created; + struct mk_list _head; +}; + +struct winevtlog_sqlite_record { + char *name; + char *bookmark_xml; + unsigned int time_updated; + unsigned int created; +}; + +/* + * Open a Windows Event Log channel. + */ +struct winevtlog_channel *winevtlog_open(const char *channel); +void winevtlog_close(struct winevtlog_channel *ch); + +/* + * Read records from a channel. + */ +int winevtlog_read(struct winevtlog_channel *ch, + struct winevtlog_config *ctx, unsigned int *read); + +/* + * A bulk API to handle multiple channels at once using mk_list. + * + * "channels" are comma-separated names like "Setup,Security". + */ +struct mk_list *winevtlog_open_all(const char *channels, struct winevtlog_config *ctx); +void winevtlog_close_all(struct mk_list *list); + +void winevtlog_pack_xml_event(WCHAR *system_xml, WCHAR *message, + PEVT_VARIANT string_inserts, UINT count_inserts, struct winevtlog_channel *ch, + struct winevtlog_config *ctx); +void winevtlog_pack_event(PEVT_VARIANT system, WCHAR *message, + PEVT_VARIANT string_inserts, UINT count_inserts, struct winevtlog_channel *ch, + struct winevtlog_config *ctx); + +/* + * Save the read offset to disk. + */ +int winevtlog_sqlite_load(struct winevtlog_channel *ch, struct flb_sqldb *db); +int winevtlog_sqlite_save(struct winevtlog_channel *ch, struct flb_sqldb *db); + +/* + * SQL templates + */ +#define SQL_CREATE_CHANNELS \ + "CREATE TABLE IF NOT EXISTS in_winevtlog_channels (" \ + " name TEXT PRIMARY KEY," \ + " bookmark_xml TEXT," \ + " time_updated INTEGER," \ + " created INTEGER" \ + ");" + +#define SQL_GET_CHANNEL \ + "SELECT name, bookmark_xml, time_updated, created" \ + " FROM in_winevtlog_channels WHERE name = '%s';" + +/* + * This uses UPCERT i.e. execute INSERT first and fall back to + * UPDATE if the entry already exists. It saves the trouble of + * doing an existence check manually. + * + * https://www.sqlite.org/lang_UPSERT.html + */ +#define SQL_UPDATE_CHANNEL \ + "INSERT INTO in_winevtlog_channels" \ + " (name, bookmark_xml, time_updated, created)" \ + " VALUES ('%s', \"%s\", %u, %llu)" \ + " ON CONFLICT(name) DO UPDATE" \ + " SET bookmark_xml = excluded.bookmark_xml," \ + " time_updated = excluded.time_updated" \ + +#endif |