| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "windows-events.h" |
| 4 | |
| 5 | static void wevt_event_done(WEVT_LOG *log); |
| 6 | |
| 7 | static uint64_t wevt_log_file_size(const wchar_t *channel); |
| 8 | |
| 9 | // -------------------------------------------------------------------------------------------------------------------- |
| 10 | |
| 11 | static const char *EvtGetExtendedStatus_utf8(void) { |
| 12 | static __thread wchar_t wbuf[4096]; |
| 13 | static __thread char buf[4096]; |
| 14 | DWORD wbuf_used = 0; |
| 15 | |
| 16 | if(EvtGetExtendedStatus(sizeof(wbuf) / sizeof(wchar_t), wbuf, &wbuf_used) == ERROR_SUCCESS) { |
| 17 | wbuf[sizeof(wbuf) / sizeof(wchar_t) - 1] = 0; |
| 18 | unicode2utf8(buf, sizeof(buf), wbuf); |
| 19 | } |
| 20 | else |
| 21 | buf[0] = '\0'; |
| 22 | |
| 23 | // the EvtGetExtendedStatus() may be successful with an empty message |
| 24 | if(!buf[0]) |
| 25 | strncpyz(buf, "no additional information", sizeof(buf) - 1); |
| 26 | |
| 27 | return buf; |
| 28 | } |
| 29 | |
| 30 | // -------------------------------------------------------------------------------------------------------------------- |
| 31 | |
| 32 | bool EvtFormatMessage_utf16( |
| 33 | TXT_UTF16 *dst, EVT_HANDLE hMetadata, EVT_HANDLE hEvent, DWORD dwMessageId, EVT_FORMAT_MESSAGE_FLAGS flags) { |
| 34 | dst->used = 0; |
| 35 | |
| 36 | DWORD size = 0; |
| 37 | if(!dst->data) { |
| 38 | EvtFormatMessage(hMetadata, hEvent, dwMessageId, 0, NULL, flags, 0, NULL, &size); |
| 39 | if(!size) { |
| 40 | // nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtFormatMessage() to get message size failed."); |
| 41 | goto cleanup; |
| 42 | } |
| 43 | txt_utf16_resize(dst, size, false); |
| 44 | } |
| 45 | |
| 46 | // First, try to get the message using the existing buffer |
| 47 | if (!EvtFormatMessage(hMetadata, hEvent, dwMessageId, 0, NULL, flags, dst->size, dst->data, &size) || !dst->data) { |
| 48 | if (dst->data && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { |
| 49 | // nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtFormatMessage() failed."); |
| 50 | goto cleanup; |
| 51 | } |
| 52 | |
| 53 | // Try again with the resized buffer |
| 54 | txt_utf16_resize(dst, size, false); |
| 55 | if (!EvtFormatMessage(hMetadata, hEvent, dwMessageId, 0, NULL, flags, dst->size, dst->data, &size)) { |
| 56 | // nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtFormatMessage() failed after resizing buffer."); |
| 57 | goto cleanup; |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | // make sure it is null terminated |
| 62 | if(size <= dst->size) |
| 63 | dst->data[size - 1] = 0; |
| 64 | else |
| 65 | dst->data[dst->size - 1] = 0; |
| 66 | |
| 67 | // unfortunately we have to calculate the length every time |
| 68 | // the size returned may not be the length of the dst string |
| 69 | dst->used = wcslen(dst->data) + 1; |
| 70 | |
| 71 | return true; |
| 72 | |
| 73 | cleanup: |
| 74 | dst->used = 0; |
| 75 | return false; |
| 76 | } |
| 77 | |
| 78 | static bool EvtFormatMessage_utf8( |
| 79 | TXT_UTF16 *tmp, PROVIDER_META_HANDLE *p, EVT_HANDLE hEvent, |
| 80 | TXT_UTF8 *dst, EVT_FORMAT_MESSAGE_FLAGS flags) { |
| 81 | |
| 82 | dst->src = TXT_SOURCE_EVENT_LOG; |
| 83 | |
| 84 | if(EvtFormatMessage_utf16(tmp, provider_handle(p), hEvent, 0, flags)) |
| 85 | return txt_utf16_to_utf8(dst, tmp); |
| 86 | |
| 87 | txt_utf8_empty(dst); |
| 88 | return false; |
| 89 | } |
| 90 | |
| 91 | bool EvtFormatMessage_Event_utf8(TXT_UTF16 *tmp, PROVIDER_META_HANDLE *p, EVT_HANDLE hEvent, TXT_UTF8 *dst) { |
| 92 | return EvtFormatMessage_utf8(tmp, p, hEvent, dst, EvtFormatMessageEvent); |
| 93 | } |
| 94 | |
| 95 | bool EvtFormatMessage_Xml_utf8(TXT_UTF16 *tmp, PROVIDER_META_HANDLE *p, EVT_HANDLE hEvent, TXT_UTF8 *dst) { |
| 96 | return EvtFormatMessage_utf8(tmp, p, hEvent, dst, EvtFormatMessageXml); |
| 97 | } |
| 98 | |
| 99 | // -------------------------------------------------------------------------------------------------------------------- |
| 100 | |
| 101 | static void wevt_get_field_from_cache( |
| 102 | WEVT_LOG *log, uint64_t value, PROVIDER_META_HANDLE *h, |
| 103 | TXT_UTF8 *dst, const ND_UUID *provider, |
| 104 | WEVT_FIELD_TYPE cache_type, EVT_FORMAT_MESSAGE_FLAGS flags) { |
| 105 | |
| 106 | if (field_cache_get(cache_type, provider, value, dst)) |
| 107 | return; |
| 108 | |
| 109 | EvtFormatMessage_utf8(&log->ops.unicode, h, log->hEvent, dst, flags); |
| 110 | field_cache_set(cache_type, provider, value, dst); |
| 111 | } |
| 112 | |
| 113 | // -------------------------------------------------------------------------------------------------------------------- |
| 114 | // Level |
| 115 | |
| 116 | #define SET_LEN_AND_RETURN(constant) *len = sizeof(constant) - 1; return constant |
| 117 | |
| 118 | static inline const char *wevt_level_hardcoded(uint64_t level, size_t *len) { |
| 119 | switch(level) { |
| 120 | case WEVT_LEVEL_NONE: SET_LEN_AND_RETURN(WEVT_LEVEL_NAME_NONE); |
| 121 | case WEVT_LEVEL_CRITICAL: SET_LEN_AND_RETURN(WEVT_LEVEL_NAME_CRITICAL); |
| 122 | case WEVT_LEVEL_ERROR: SET_LEN_AND_RETURN(WEVT_LEVEL_NAME_ERROR); |
| 123 | case WEVT_LEVEL_WARNING: SET_LEN_AND_RETURN(WEVT_LEVEL_NAME_WARNING); |
| 124 | case WEVT_LEVEL_INFORMATION: SET_LEN_AND_RETURN(WEVT_LEVEL_NAME_INFORMATION); |
| 125 | case WEVT_LEVEL_VERBOSE: SET_LEN_AND_RETURN(WEVT_LEVEL_NAME_VERBOSE); |
| 126 | default: *len = 0; return NULL; |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | static void wevt_get_level(WEVT_LOG *log, WEVT_EVENT *ev, PROVIDER_META_HANDLE *h) { |
| 131 | TXT_UTF8 *dst = &log->ops.level; |
| 132 | uint64_t value = ev->level; |
| 133 | |
| 134 | txt_utf8_empty(dst); |
| 135 | |
| 136 | EVT_FORMAT_MESSAGE_FLAGS flags = EvtFormatMessageLevel; |
| 137 | WEVT_FIELD_TYPE cache_type = WEVT_FIELD_TYPE_LEVEL; |
| 138 | bool is_provider = is_valid_provider_level(value, true); |
| 139 | |
| 140 | if(!is_provider) { |
| 141 | size_t len; |
| 142 | const char *hardcoded = wevt_level_hardcoded(value, &len); |
| 143 | if(hardcoded) { |
| 144 | txt_utf8_set(dst, hardcoded, len); |
| 145 | dst->src = TXT_SOURCE_HARDCODED; |
| 146 | } |
| 147 | else { |
| 148 | // since this is not a provider value |
| 149 | // we expect to get the system description of it |
| 150 | wevt_get_field_from_cache(log, value, h, dst, &ev->provider, cache_type, flags); |
| 151 | } |
| 152 | } |
| 153 | else if (!provider_get_level(dst, h, value)) { |
| 154 | // not found in the manifest, get it from the cache |
| 155 | wevt_get_field_from_cache(log, value, h, dst, &ev->provider, cache_type, flags); |
| 156 | } |
| 157 | |
| 158 | txt_utf8_set_numeric_if_empty( |
| 159 | dst, WEVT_PREFIX_LEVEL, sizeof(WEVT_PREFIX_LEVEL) - 1, ev->level); |
| 160 | } |
| 161 | |
| 162 | // -------------------------------------------------------------------------------------------------------------------- |
| 163 | // Opcode |
| 164 | |
| 165 | static inline const char *wevt_opcode_hardcoded(uint64_t opcode, size_t *len) { |
| 166 | switch(opcode) { |
| 167 | case WEVT_OPCODE_INFO: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_INFO); |
| 168 | case WEVT_OPCODE_START: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_START); |
| 169 | case WEVT_OPCODE_STOP: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_STOP); |
| 170 | case WEVT_OPCODE_DC_START: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_DC_START); |
| 171 | case WEVT_OPCODE_DC_STOP: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_DC_STOP); |
| 172 | case WEVT_OPCODE_EXTENSION: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_EXTENSION); |
| 173 | case WEVT_OPCODE_REPLY: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_REPLY); |
| 174 | case WEVT_OPCODE_RESUME: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_RESUME); |
| 175 | case WEVT_OPCODE_SUSPEND: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_SUSPEND); |
| 176 | case WEVT_OPCODE_SEND: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_SEND); |
| 177 | case WEVT_OPCODE_RECEIVE: SET_LEN_AND_RETURN(WEVT_OPCODE_NAME_RECEIVE); |
| 178 | default: *len = 0; return NULL; |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | static void wevt_get_opcode(WEVT_LOG *log, WEVT_EVENT *ev, PROVIDER_META_HANDLE *h) { |
| 183 | TXT_UTF8 *dst = &log->ops.opcode; |
| 184 | uint64_t value = ev->opcode; |
| 185 | |
| 186 | txt_utf8_empty(dst); |
| 187 | |
| 188 | EVT_FORMAT_MESSAGE_FLAGS flags = EvtFormatMessageOpcode; |
| 189 | WEVT_FIELD_TYPE cache_type = WEVT_FIELD_TYPE_OPCODE; |
| 190 | bool is_provider = is_valid_provider_opcode(value, true); |
| 191 | |
| 192 | if(!is_provider) { |
| 193 | size_t len; |
| 194 | const char *hardcoded = wevt_opcode_hardcoded(value, &len); |
| 195 | if(hardcoded) { |
| 196 | txt_utf8_set(dst, hardcoded, len); |
| 197 | dst->src = TXT_SOURCE_HARDCODED; |
| 198 | } |
| 199 | else { |
| 200 | // since this is not a provider value |
| 201 | // we expect to get the system description of it |
| 202 | wevt_get_field_from_cache(log, value, h, dst, &ev->provider, cache_type, flags); |
| 203 | } |
| 204 | } |
| 205 | else if (!provider_get_opcode(dst, h, value)) { |
| 206 | // not found in the manifest, get it from the cache |
| 207 | wevt_get_field_from_cache(log, value, h, dst, &ev->provider, cache_type, flags); |
| 208 | } |
| 209 | |
| 210 | txt_utf8_set_numeric_if_empty( |
| 211 | dst, WEVT_PREFIX_OPCODE, sizeof(WEVT_PREFIX_OPCODE) - 1, ev->opcode); |
| 212 | } |
| 213 | |
| 214 | // -------------------------------------------------------------------------------------------------------------------- |
| 215 | // Task |
| 216 | |
| 217 | static const char *wevt_task_hardcoded(uint64_t task, size_t *len) { |
| 218 | switch(task) { |
| 219 | case WEVT_TASK_NONE: SET_LEN_AND_RETURN(WEVT_TASK_NAME_NONE); |
| 220 | default: *len = 0; return NULL; |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | static void wevt_get_task(WEVT_LOG *log, WEVT_EVENT *ev, PROVIDER_META_HANDLE *h) { |
| 225 | TXT_UTF8 *dst = &log->ops.task; |
| 226 | uint64_t value = ev->task; |
| 227 | |
| 228 | txt_utf8_empty(dst); |
| 229 | |
| 230 | EVT_FORMAT_MESSAGE_FLAGS flags = EvtFormatMessageTask; |
| 231 | WEVT_FIELD_TYPE cache_type = WEVT_FIELD_TYPE_TASK; |
| 232 | bool is_provider = is_valid_provider_task(value, true); |
| 233 | |
| 234 | if(!is_provider) { |
| 235 | size_t len; |
| 236 | const char *hardcoded = wevt_task_hardcoded(value, &len); |
| 237 | if(hardcoded) { |
| 238 | txt_utf8_set(dst, hardcoded, len); |
| 239 | dst->src = TXT_SOURCE_HARDCODED; |
| 240 | } |
| 241 | else { |
| 242 | // since this is not a provider value |
| 243 | // we expect to get the system description of it |
| 244 | wevt_get_field_from_cache(log, value, h, dst, &ev->provider, cache_type, flags); |
| 245 | } |
| 246 | } |
| 247 | else if (!provider_get_task(dst, h, value)) { |
| 248 | // not found in the manifest, get it from the cache |
| 249 | wevt_get_field_from_cache(log, value, h, dst, &ev->provider, cache_type, flags); |
| 250 | } |
| 251 | |
| 252 | txt_utf8_set_numeric_if_empty( |
| 253 | dst, WEVT_PREFIX_TASK, sizeof(WEVT_PREFIX_TASK) - 1, ev->task); |
| 254 | } |
| 255 | |
| 256 | // -------------------------------------------------------------------------------------------------------------------- |
| 257 | // Keyword |
| 258 | |
| 259 | #define SET_BITS(msk, txt) { .mask = msk, .name = txt, .len = sizeof(txt) - 1, } |
| 260 | |
| 261 | static uint64_t wevt_keyword_handle_reserved(uint64_t value, TXT_UTF8 *dst) { |
| 262 | struct { |
| 263 | uint64_t mask; |
| 264 | const char *name; |
| 265 | size_t len; |
| 266 | } bits[] = { |
| 267 | SET_BITS(WEVT_KEYWORD_EVENTLOG_CLASSIC, WEVT_KEYWORD_NAME_EVENTLOG_CLASSIC), |
| 268 | SET_BITS(WEVT_KEYWORD_CORRELATION_HINT, WEVT_KEYWORD_NAME_CORRELATION_HINT), |
| 269 | SET_BITS(WEVT_KEYWORD_AUDIT_SUCCESS, WEVT_KEYWORD_NAME_AUDIT_SUCCESS), |
| 270 | SET_BITS(WEVT_KEYWORD_AUDIT_FAILURE, WEVT_KEYWORD_NAME_AUDIT_FAILURE), |
| 271 | SET_BITS(WEVT_KEYWORD_SQM, WEVT_KEYWORD_NAME_SQM), |
| 272 | SET_BITS(WEVT_KEYWORD_WDI_DIAG, WEVT_KEYWORD_NAME_WDI_DIAG), |
| 273 | SET_BITS(WEVT_KEYWORD_WDI_CONTEXT, WEVT_KEYWORD_NAME_WDI_CONTEXT), |
| 274 | SET_BITS(WEVT_KEYWORD_RESPONSE_TIME, WEVT_KEYWORD_NAME_RESPONSE_TIME), |
| 275 | }; |
| 276 | |
| 277 | txt_utf8_empty(dst); |
| 278 | |
| 279 | for(size_t i = 0; i < sizeof(bits) / sizeof(bits[0]) ;i++) { |
| 280 | if((value & bits[i].mask) == bits[i].mask) { |
| 281 | txt_utf8_add_keywords_separator_if_needed(dst); |
| 282 | txt_utf8_append(dst, bits[i].name, bits[i].len); |
| 283 | value &= ~(bits[i].mask); |
| 284 | dst->src = TXT_SOURCE_HARDCODED; |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | // return it without any remaining reserved bits |
| 289 | return value & 0x0000FFFFFFFFFFFF; |
| 290 | } |
| 291 | |
| 292 | static void wevt_get_keyword(WEVT_LOG *log, WEVT_EVENT *ev, PROVIDER_META_HANDLE *h) { |
| 293 | TXT_UTF8 *dst = &log->ops.keywords; |
| 294 | |
| 295 | if(ev->keywords == WEVT_KEYWORD_NONE) { |
| 296 | txt_utf8_set(dst, WEVT_KEYWORD_NAME_NONE, sizeof(WEVT_KEYWORD_NAME_NONE) - 1); |
| 297 | dst->src = TXT_SOURCE_HARDCODED; |
| 298 | } |
| 299 | |
| 300 | uint64_t value = wevt_keyword_handle_reserved(ev->keywords, dst); |
| 301 | |
| 302 | EVT_FORMAT_MESSAGE_FLAGS flags = EvtFormatMessageKeyword; |
| 303 | WEVT_FIELD_TYPE cache_type = WEVT_FIELD_TYPE_KEYWORD; |
| 304 | |
| 305 | if(!value && dst->used <= 1) { |
| 306 | // no hardcoded info in the buffer, make it None |
| 307 | txt_utf8_set(dst, WEVT_KEYWORD_NAME_NONE, sizeof(WEVT_KEYWORD_NAME_NONE) - 1); |
| 308 | dst->src = TXT_SOURCE_HARDCODED; |
| 309 | } |
| 310 | else if (value && !provider_get_keywords(dst, h, value) && dst->used <= 1) { |
| 311 | // the provider did not provide any info and the description is still empty. |
| 312 | // the system returns 1 keyword, the highest bit, not a list |
| 313 | // so, when we call the system, we pass the original value (ev->keywords) |
| 314 | wevt_get_field_from_cache(log, ev->keywords, h, dst, &ev->provider, cache_type, flags); |
| 315 | } |
| 316 | |
| 317 | txt_utf8_set_hex_if_empty( |
| 318 | dst, WEVT_PREFIX_KEYWORDS, sizeof(WEVT_PREFIX_KEYWORDS) - 1, ev->keywords); |
| 319 | } |
| 320 | |
| 321 | // -------------------------------------------------------------------------------------------------------------------- |
| 322 | // Fetching Events |
| 323 | |
| 324 | static inline bool wEvtRender(WEVT_LOG *log, EVT_HANDLE context, WEVT_VARIANT *raw) { |
| 325 | DWORD bytes_used = 0, property_count = 0; |
| 326 | if (!EvtRender(context, log->hEvent, EvtRenderEventValues, raw->size, raw->data, &bytes_used, &property_count)) { |
| 327 | // information exceeds the allocated space |
| 328 | if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { |
| 329 | nd_log(NDLS_COLLECTORS, NDLP_ERR, |
| 330 | "EvtRender() failed, hRenderSystemContext: 0x%lx, hEvent: 0x%lx, content: 0x%lx, size: %u, extended info: %s", |
| 331 | (uintptr_t)context, (uintptr_t)log->hEvent, (uintptr_t)raw->data, raw->size, |
| 332 | EvtGetExtendedStatus_utf8()); |
| 333 | return false; |
| 334 | } |
| 335 | |
| 336 | wevt_variant_resize(raw, bytes_used); |
| 337 | if (!EvtRender(context, log->hEvent, EvtRenderEventValues, raw->size, raw->data, &bytes_used, &property_count)) { |
| 338 | nd_log(NDLS_COLLECTORS, NDLP_ERR, |
| 339 | "EvtRender() failed, after bytes_used increase, extended info: %s", |
| 340 | EvtGetExtendedStatus_utf8()); |
| 341 | return false; |
| 342 | } |
| 343 | } |
| 344 | raw->used = bytes_used; |
| 345 | raw->count = property_count; |
| 346 | |
| 347 | return true; |
| 348 | } |
| 349 | |
| 350 | static bool wevt_get_next_event_one(WEVT_LOG *log, WEVT_EVENT *ev) { |
| 351 | bool ret = false; |
| 352 | |
| 353 | if(!wEvtRender(log, log->hRenderSystemContext, &log->ops.raw.system)) |
| 354 | goto cleanup; |
| 355 | |
| 356 | EVT_VARIANT *content = log->ops.raw.system.data; |
| 357 | |
| 358 | ev->id = wevt_field_get_uint64(&content[EvtSystemEventRecordId]); |
| 359 | ev->event_id = wevt_field_get_uint16(&content[EvtSystemEventID]); |
| 360 | ev->level = wevt_field_get_uint8(&content[EvtSystemLevel]); |
| 361 | ev->opcode = wevt_field_get_uint8(&content[EvtSystemOpcode]); |
| 362 | ev->keywords = wevt_field_get_uint64_hex(&content[EvtSystemKeywords]); |
| 363 | ev->version = wevt_field_get_uint8(&content[EvtSystemVersion]); |
| 364 | ev->task = wevt_field_get_uint16(&content[EvtSystemTask]); |
| 365 | ev->qualifiers = wevt_field_get_uint16(&content[EvtSystemQualifiers]); |
| 366 | ev->process_id = wevt_field_get_uint32(&content[EvtSystemProcessID]); |
| 367 | ev->thread_id = wevt_field_get_uint32(&content[EvtSystemThreadID]); |
| 368 | ev->created_ns = wevt_field_get_filetime_to_ns(&content[EvtSystemTimeCreated]); |
| 369 | |
| 370 | if(log->type & WEVT_QUERY_EXTENDED) { |
| 371 | wevt_field_get_string_utf8(&content[EvtSystemChannel], &log->ops.channel); |
| 372 | wevt_field_get_string_utf8(&content[EvtSystemComputer], &log->ops.computer); |
| 373 | wevt_field_get_string_utf8(&content[EvtSystemProviderName], &log->ops.provider); |
| 374 | wevt_get_uuid_by_type(&content[EvtSystemProviderGuid], &ev->provider); |
| 375 | wevt_get_uuid_by_type(&content[EvtSystemActivityID], &ev->activity_id); |
| 376 | wevt_get_uuid_by_type(&content[EvtSystemRelatedActivityID], &ev->related_activity_id); |
| 377 | wevt_field_get_sid(&content[EvtSystemUserID], &log->ops.account, &log->ops.domain, &log->ops.sid); |
| 378 | |
| 379 | PROVIDER_META_HANDLE *p = log->provider = |
| 380 | provider_get(ev->provider, content[EvtSystemProviderName].StringVal); |
| 381 | |
| 382 | ev->platform = provider_get_platform(p); |
| 383 | |
| 384 | wevt_get_level(log, ev, p); |
| 385 | wevt_get_task(log, ev, p); |
| 386 | wevt_get_opcode(log, ev, p); |
| 387 | wevt_get_keyword(log, ev, p); |
| 388 | |
| 389 | if(log->type & WEVT_QUERY_EVENT_DATA && wEvtRender(log, log->hRenderUserContext, &log->ops.raw.user)) { |
| 390 | #if (ON_FTS_PRELOAD_MESSAGE == 1) |
| 391 | EvtFormatMessage_Event_utf8(&log->ops.unicode, log->provider, log->hEvent, &log->ops.event); |
| 392 | #endif |
| 393 | #if (ON_FTS_PRELOAD_XML == 1) |
| 394 | EvtFormatMessage_Xml_utf8(&log->ops.unicode, log->provider, log->hEvent, &log->ops.xml); |
| 395 | #endif |
| 396 | #if (ON_FTS_PRELOAD_EVENT_DATA == 1) |
| 397 | for(size_t i = 0; i < log->ops.raw.user.count ;i++) |
| 398 | evt_variant_to_buffer(log->ops.event_data, &log->ops.raw.user.data[i], " ||| "); |
| 399 | #endif |
| 400 | } |
| 401 | } |
| 402 | |
| 403 | ret = true; |
| 404 | |
| 405 | cleanup: |
| 406 | return ret; |
| 407 | } |
| 408 | |
| 409 | bool wevt_get_next_event(WEVT_LOG *log, WEVT_EVENT *ev) { |
| 410 | DWORD size = (log->type & WEVT_QUERY_EXTENDED) ? BATCH_NEXT_EVENT : 1; |
| 411 | DWORD max_failures = 10; |
| 412 | |
| 413 | fatal_assert(log && log->hQuery && log->hRenderSystemContext); |
| 414 | |
| 415 | while(max_failures > 0) { |
| 416 | if (log->batch.used >= log->batch.size) { |
| 417 | log->batch.size = 0; |
| 418 | log->batch.used = 0; |
| 419 | DWORD err; |
| 420 | if(!EvtNext(log->hQuery, size, log->batch.hEvents, INFINITE, 0, &log->batch.size)) { |
| 421 | err = GetLastError(); |
| 422 | if(err == ERROR_NO_MORE_ITEMS) |
| 423 | return false; // no data available, return failure |
| 424 | } |
| 425 | |
| 426 | if(!log->batch.size) { |
| 427 | if(size == 1) { |
| 428 | nd_log(NDLS_COLLECTORS, NDLP_ERR, |
| 429 | "EvtNext() failed, hQuery: 0x%lx, size: %zu, extended info: %s", |
| 430 | (uintptr_t)log->hQuery, (size_t)size, EvtGetExtendedStatus_utf8()); |
| 431 | return false; |
| 432 | } |
| 433 | |
| 434 | // EvtNext() returns true when it can full the array |
| 435 | // so, let's retry with a smaller array. |
| 436 | size /= 2; |
| 437 | if(size < 1) size = 1; |
| 438 | continue; |
| 439 | } |
| 440 | } |
| 441 | |
| 442 | log->query_stats.event_count++; |
| 443 | log->log_stats.event_count++; |
| 444 | |
| 445 | // cleanup any previous event data |
| 446 | wevt_event_done(log); |
| 447 | |
| 448 | log->hEvent = log->batch.hEvents[log->batch.used]; |
| 449 | log->batch.hEvents[log->batch.used] = NULL; |
| 450 | log->batch.used++; |
| 451 | |
| 452 | if(wevt_get_next_event_one(log, ev)) |
| 453 | return true; |
| 454 | else { |
| 455 | log->query_stats.failed_count++; |
| 456 | log->log_stats.failed_count++; |
| 457 | max_failures--; |
| 458 | } |
| 459 | } |
| 460 | |
| 461 | return false; |
| 462 | } |
| 463 | |
| 464 | static void wevt_event_done(WEVT_LOG *log) { |
| 465 | if (log->provider) { |
| 466 | provider_release(log->provider); |
| 467 | log->provider = NULL; |
| 468 | } |
| 469 | |
| 470 | if (log->hEvent) { |
| 471 | EvtClose(log->hEvent); |
| 472 | log->hEvent = NULL; |
| 473 | } |
| 474 | |
| 475 | log->ops.channel.src = TXT_SOURCE_UNKNOWN; |
| 476 | log->ops.provider.src = TXT_SOURCE_UNKNOWN; |
| 477 | log->ops.computer.src = TXT_SOURCE_UNKNOWN; |
| 478 | log->ops.account.src = TXT_SOURCE_UNKNOWN; |
| 479 | log->ops.domain.src = TXT_SOURCE_UNKNOWN; |
| 480 | log->ops.sid.src = TXT_SOURCE_UNKNOWN; |
| 481 | |
| 482 | log->ops.event.src = TXT_SOURCE_UNKNOWN; |
| 483 | log->ops.level.src = TXT_SOURCE_UNKNOWN; |
| 484 | log->ops.keywords.src = TXT_SOURCE_UNKNOWN; |
| 485 | log->ops.opcode.src = TXT_SOURCE_UNKNOWN; |
| 486 | log->ops.task.src = TXT_SOURCE_UNKNOWN; |
| 487 | log->ops.xml.src = TXT_SOURCE_UNKNOWN; |
| 488 | |
| 489 | log->ops.channel.used = 0; |
| 490 | log->ops.provider.used = 0; |
| 491 | log->ops.computer.used = 0; |
| 492 | log->ops.account.used = 0; |
| 493 | log->ops.domain.used = 0; |
| 494 | log->ops.sid.used = 0; |
| 495 | |
| 496 | log->ops.event.used = 0; |
| 497 | log->ops.level.used = 0; |
| 498 | log->ops.keywords.used = 0; |
| 499 | log->ops.opcode.used = 0; |
| 500 | log->ops.task.used = 0; |
| 501 | log->ops.xml.used = 0; |
| 502 | |
| 503 | if(log->ops.event_data) |
| 504 | log->ops.event_data->len = 0; |
| 505 | } |
| 506 | |
| 507 | // -------------------------------------------------------------------------------------------------------------------- |
| 508 | // Query management |
| 509 | |
| 510 | bool wevt_query(WEVT_LOG *log, LPCWSTR channel, LPCWSTR query, EVT_QUERY_FLAGS direction) { |
| 511 | wevt_query_done(log); |
| 512 | log->log_stats.queries_count++; |
| 513 | |
| 514 | EVT_HANDLE hQuery = EvtQuery(NULL, channel, query, EvtQueryChannelPath | (direction & (EvtQueryReverseDirection | EvtQueryForwardDirection)) | EvtQueryTolerateQueryErrors); |
| 515 | if (!hQuery) { |
| 516 | nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtQuery() failed, query: %s | extended info: %s", |
| 517 | query2utf8(query), EvtGetExtendedStatus_utf8()); |
| 518 | |
| 519 | log->log_stats.queries_failed++; |
| 520 | return false; |
| 521 | } |
| 522 | |
| 523 | log->hQuery = hQuery; |
| 524 | return true; |
| 525 | } |
| 526 | |
| 527 | void wevt_query_done(WEVT_LOG *log) { |
| 528 | // close the last working hEvent |
| 529 | wevt_event_done(log); |
| 530 | |
| 531 | // close all batched hEvents |
| 532 | for(DWORD i = log->batch.used; i < log->batch.size ;i++) { |
| 533 | if(log->batch.hEvents[i]) |
| 534 | EvtClose(log->batch.hEvents[i]); |
| 535 | |
| 536 | log->batch.hEvents[i] = NULL; |
| 537 | } |
| 538 | log->batch.used = 0; |
| 539 | log->batch.size = 0; |
| 540 | |
| 541 | if (log->hQuery) { |
| 542 | EvtClose(log->hQuery); |
| 543 | log->hQuery = NULL; |
| 544 | } |
| 545 | |
| 546 | log->query_stats.event_count = 0; |
| 547 | log->query_stats.failed_count = 0; |
| 548 | } |
| 549 | |
| 550 | // -------------------------------------------------------------------------------------------------------------------- |
| 551 | // Log management |
| 552 | |
| 553 | WEVT_LOG *wevt_openlog6(WEVT_QUERY_TYPE type) { |
| 554 | WEVT_LOG *log = callocz(1, sizeof(*log)); |
| 555 | log->type = type; |
| 556 | |
| 557 | // create the system render |
| 558 | log->hRenderSystemContext = EvtCreateRenderContext(0, NULL, EvtRenderContextSystem); |
| 559 | if (!log->hRenderSystemContext) { |
| 560 | nd_log(NDLS_COLLECTORS, NDLP_ERR, |
| 561 | "EvtCreateRenderContext() on system context failed, extended info: %s", |
| 562 | EvtGetExtendedStatus_utf8()); |
| 563 | goto cleanup; |
| 564 | } |
| 565 | |
| 566 | if(type & WEVT_QUERY_EVENT_DATA) { |
| 567 | log->hRenderUserContext = EvtCreateRenderContext(0, NULL, EvtRenderContextUser); |
| 568 | if (!log->hRenderUserContext) { |
| 569 | nd_log(NDLS_COLLECTORS, NDLP_ERR, |
| 570 | "EvtCreateRenderContext failed, on user context failed, extended info: %s", |
| 571 | EvtGetExtendedStatus_utf8()); |
| 572 | goto cleanup; |
| 573 | } |
| 574 | |
| 575 | log->ops.event_data = buffer_create(4096, NULL); |
| 576 | } |
| 577 | |
| 578 | return log; |
| 579 | |
| 580 | cleanup: |
| 581 | wevt_closelog6(log); |
| 582 | return NULL; |
| 583 | } |
| 584 | |
| 585 | void wevt_closelog6(WEVT_LOG *log) { |
| 586 | wevt_query_done(log); |
| 587 | |
| 588 | if (log->hRenderSystemContext) |
| 589 | EvtClose(log->hRenderSystemContext); |
| 590 | |
| 591 | if (log->hRenderUserContext) |
| 592 | EvtClose(log->hRenderUserContext); |
| 593 | |
| 594 | wevt_variant_cleanup(&log->ops.raw.system); |
| 595 | wevt_variant_cleanup(&log->ops.raw.user); |
| 596 | txt_utf16_cleanup(&log->ops.unicode); |
| 597 | txt_utf8_cleanup(&log->ops.channel); |
| 598 | txt_utf8_cleanup(&log->ops.provider); |
| 599 | txt_utf8_cleanup(&log->ops.computer); |
| 600 | txt_utf8_cleanup(&log->ops.account); |
| 601 | txt_utf8_cleanup(&log->ops.domain); |
| 602 | txt_utf8_cleanup(&log->ops.sid); |
| 603 | |
| 604 | txt_utf8_cleanup(&log->ops.event); |
| 605 | txt_utf8_cleanup(&log->ops.level); |
| 606 | txt_utf8_cleanup(&log->ops.keywords); |
| 607 | txt_utf8_cleanup(&log->ops.opcode); |
| 608 | txt_utf8_cleanup(&log->ops.task); |
| 609 | txt_utf8_cleanup(&log->ops.xml); |
| 610 | |
| 611 | buffer_free(log->ops.event_data); |
| 612 | |
| 613 | freez(log); |
| 614 | } |
| 615 | |
| 616 | // -------------------------------------------------------------------------------------------------------------------- |
| 617 | // Retention |
| 618 | |
| 619 | bool wevt_channel_retention(WEVT_LOG *log, const wchar_t *channel, const wchar_t *query, EVT_RETENTION *retention) { |
| 620 | bool ret = false; |
| 621 | |
| 622 | // get the number of the oldest record in the log |
| 623 | // "EvtGetLogInfo()" does not work properly with "EvtLogOldestRecordNumber" |
| 624 | // we have to get it from the first EventRecordID |
| 625 | |
| 626 | // query the eventlog |
| 627 | log->hQuery = EvtQuery(NULL, channel, query, EvtQueryChannelPath | EvtQueryForwardDirection | EvtQueryTolerateQueryErrors); |
| 628 | if (!log->hQuery) { |
| 629 | if (GetLastError() == ERROR_EVT_CHANNEL_NOT_FOUND) |
| 630 | nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtQuery() for retention failed, channel '%s' not found, cannot get retention, extended info: %s", |
| 631 | channel2utf8(channel), EvtGetExtendedStatus_utf8()); |
| 632 | else |
| 633 | nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtQuery() for retention on channel '%s' failed, cannot get retention, extended info: %s", |
| 634 | channel2utf8(channel), EvtGetExtendedStatus_utf8()); |
| 635 | |
| 636 | goto cleanup; |
| 637 | } |
| 638 | |
| 639 | if (!wevt_get_next_event(log, &retention->first_event)) |
| 640 | goto cleanup; |
| 641 | |
| 642 | if (!retention->first_event.id) { |
| 643 | // no data in the event log |
| 644 | retention->first_event = retention->last_event = WEVT_EVENT_EMPTY; |
| 645 | ret = true; |
| 646 | goto cleanup; |
| 647 | } |
| 648 | EvtClose(log->hQuery); |
| 649 | |
| 650 | log->hQuery = EvtQuery(NULL, channel, query, EvtQueryChannelPath | EvtQueryReverseDirection | EvtQueryTolerateQueryErrors); |
| 651 | if (!log->hQuery) { |
| 652 | if (GetLastError() == ERROR_EVT_CHANNEL_NOT_FOUND) |
| 653 | nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtQuery() for retention failed, channel '%s' not found, extended info: %s", |
| 654 | channel2utf8(channel), EvtGetExtendedStatus_utf8()); |
| 655 | else |
| 656 | nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtQuery() for retention on channel '%s' failed, extended info: %s", |
| 657 | channel2utf8(channel), EvtGetExtendedStatus_utf8()); |
| 658 | |
| 659 | goto cleanup; |
| 660 | } |
| 661 | |
| 662 | if (!wevt_get_next_event(log, &retention->last_event) || retention->last_event.id == 0) { |
| 663 | // no data in eventlog |
| 664 | retention->last_event = retention->first_event; |
| 665 | } |
| 666 | retention->last_event.id += 1; // we should read the last record |
| 667 | ret = true; |
| 668 | |
| 669 | cleanup: |
| 670 | wevt_query_done(log); |
| 671 | |
| 672 | if(ret) { |
| 673 | retention->entries = (channel && !query) ? retention->last_event.id - retention->first_event.id : 0; |
| 674 | |
| 675 | if(retention->last_event.created_ns >= retention->first_event.created_ns) |
| 676 | retention->duration_ns = retention->last_event.created_ns - retention->first_event.created_ns; |
| 677 | else |
| 678 | retention->duration_ns = retention->first_event.created_ns - retention->last_event.created_ns; |
| 679 | |
| 680 | retention->size_bytes = wevt_log_file_size(channel); |
| 681 | } |
| 682 | else |
| 683 | memset(retention, 0, sizeof(*retention)); |
| 684 | |
| 685 | return ret; |
| 686 | } |
| 687 | |
| 688 | static uint64_t wevt_log_file_size(const wchar_t *channel) { |
| 689 | EVT_HANDLE hLog = NULL; |
| 690 | EVT_VARIANT evtVariant; |
| 691 | DWORD bufferUsed = 0; |
| 692 | uint64_t file_size = 0; |
| 693 | |
| 694 | // Open the event log channel |
| 695 | hLog = EvtOpenLog(NULL, channel, EvtOpenChannelPath); |
| 696 | if (!hLog) { |
| 697 | nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtOpenLog() on channel '%s' failed, extended info: %s", |
| 698 | channel2utf8(channel), EvtGetExtendedStatus_utf8()); |
| 699 | goto cleanup; |
| 700 | } |
| 701 | |
| 702 | // Get the file size of the log |
| 703 | if (!EvtGetLogInfo(hLog, EvtLogFileSize, sizeof(evtVariant), &evtVariant, &bufferUsed)) { |
| 704 | nd_log(NDLS_COLLECTORS, NDLP_ERR, "EvtGetLogInfo() on channel '%s' failed, extended info: %s", |
| 705 | channel2utf8(channel), EvtGetExtendedStatus_utf8()); |
| 706 | goto cleanup; |
| 707 | } |
| 708 | |
| 709 | // Extract the file size from the EVT_VARIANT structure |
| 710 | file_size = evtVariant.UInt64Val; |
| 711 | |
| 712 | cleanup: |
| 713 | if (hLog) |
| 714 | EvtClose(hLog); |
| 715 | |
| 716 | return file_size; |
| 717 | } |