| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "nd_log-internals.h" |
| 4 | |
| 5 | #if defined(OS_WINDOWS) && (defined(HAVE_ETW) || defined(HAVE_WEL)) |
| 6 | |
| 7 | // -------------------------------------------------------------------------------------------------------------------- |
| 8 | // construct an event id |
| 9 | |
| 10 | // load message resources generated header |
| 11 | #include "wevt_netdata.h" |
| 12 | |
| 13 | // include the common definitions with the message resources and manifest generator |
| 14 | #include "nd_log-to-windows-common.h" |
| 15 | |
| 16 | #if defined(HAVE_ETW) |
| 17 | // we need the manifest, only in ETW mode |
| 18 | |
| 19 | // eliminate compiler warnings and load manifest generated header |
| 20 | #undef EXTERN_C |
| 21 | #define EXTERN_C |
| 22 | #undef __declspec |
| 23 | #define __declspec(x) |
| 24 | #include "wevt_netdata_manifest.h" |
| 25 | |
| 26 | static REGHANDLE regHandle; |
| 27 | #endif |
| 28 | |
| 29 | // Function to construct EventID |
| 30 | static DWORD complete_event_id(DWORD facility, DWORD severity, DWORD event_code) { |
| 31 | DWORD event_id = 0; |
| 32 | |
| 33 | // Set Severity |
| 34 | event_id |= ((DWORD)(severity) << EVENT_ID_SEV_SHIFT) & EVENT_ID_SEV_MASK; |
| 35 | |
| 36 | // Set Customer Code Flag (C) |
| 37 | event_id |= (0x0 << EVENT_ID_C_SHIFT) & EVENT_ID_C_MASK; |
| 38 | |
| 39 | // Set Reserved Bit (R) - typically 0 |
| 40 | event_id |= (0x0 << EVENT_ID_R_SHIFT) & EVENT_ID_R_MASK; |
| 41 | |
| 42 | // Set Facility |
| 43 | event_id |= ((DWORD)(facility) << EVENT_ID_FACILITY_SHIFT) & EVENT_ID_FACILITY_MASK; |
| 44 | |
| 45 | // Set Code |
| 46 | event_id |= ((DWORD)(event_code) << EVENT_ID_CODE_SHIFT) & EVENT_ID_CODE_MASK; |
| 47 | |
| 48 | return event_id; |
| 49 | } |
| 50 | |
| 51 | DWORD construct_event_id(ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, MESSAGE_ID messageID) { |
| 52 | DWORD event_code = construct_event_code(source, priority, messageID); |
| 53 | return complete_event_id(FACILITY_NETDATA, get_severity_from_priority(priority), event_code); |
| 54 | } |
| 55 | |
| 56 | static bool check_event_id(ND_LOG_SOURCES source __maybe_unused, ND_LOG_FIELD_PRIORITY priority __maybe_unused, MESSAGE_ID messageID __maybe_unused, DWORD event_code __maybe_unused) { |
| 57 | #ifdef NETDATA_INTERNAL_CHECKS |
| 58 | DWORD generated = construct_event_id(source, priority, messageID); |
| 59 | if(generated != event_code) { |
| 60 | |
| 61 | // this is just used for a break point, to see the values in hex |
| 62 | char current[UINT64_HEX_MAX_LENGTH]; |
| 63 | print_uint64_hex(current, generated); |
| 64 | |
| 65 | char wanted[UINT64_HEX_MAX_LENGTH]; |
| 66 | print_uint64_hex(wanted, event_code); |
| 67 | |
| 68 | const char *got = current; |
| 69 | const char *good = wanted; |
| 70 | internal_fatal(true, "EventIDs mismatch, expected %s, got %s", good, got); |
| 71 | } |
| 72 | #endif |
| 73 | |
| 74 | return true; |
| 75 | } |
| 76 | |
| 77 | // -------------------------------------------------------------------------------------------------------------------- |
| 78 | // initialization |
| 79 | |
| 80 | // Define provider names per source (only when not using ETW) |
| 81 | static const wchar_t *wel_provider_per_source[_NDLS_MAX] = { |
| 82 | [NDLS_UNSET] = NULL, // not used, linked to NDLS_DAEMON |
| 83 | [NDLS_ACCESS] = NETDATA_WEL_PROVIDER_ACCESS_W, // |
| 84 | [NDLS_ACLK] = NETDATA_WEL_PROVIDER_ACLK_W, // |
| 85 | [NDLS_COLLECTORS] = NETDATA_WEL_PROVIDER_COLLECTORS_W,// |
| 86 | [NDLS_DAEMON] = NETDATA_WEL_PROVIDER_DAEMON_W, // |
| 87 | [NDLS_HEALTH] = NETDATA_WEL_PROVIDER_HEALTH_W, // |
| 88 | [NDLS_DEBUG] = NULL, // used, linked to NDLS_DAEMON |
| 89 | }; |
| 90 | |
| 91 | bool wel_replace_program_with_wevt_netdata_dll(wchar_t *str, size_t size) { |
| 92 | const wchar_t *replacement = L"\\wevt_netdata.dll"; |
| 93 | |
| 94 | // Find the last occurrence of '\\' to isolate the filename |
| 95 | wchar_t *lastBackslash = wcsrchr(str, L'\\'); |
| 96 | |
| 97 | if (lastBackslash != NULL) { |
| 98 | // Calculate new length after replacement |
| 99 | size_t newLen = (lastBackslash - str) + wcslen(replacement); |
| 100 | |
| 101 | // Ensure new length does not exceed buffer size |
| 102 | if (newLen >= size) |
| 103 | return false; // Not enough space in the buffer |
| 104 | |
| 105 | // Terminate the string at the last backslash |
| 106 | *lastBackslash = L'\0'; |
| 107 | |
| 108 | // Append the replacement filename |
| 109 | wcsncat(str, replacement, size - wcslen(str) - 1); |
| 110 | |
| 111 | // Check if the new file exists |
| 112 | if (GetFileAttributesW(str) != INVALID_FILE_ATTRIBUTES) |
| 113 | return true; // The file exists |
| 114 | else |
| 115 | return false; // The file does not exist |
| 116 | } |
| 117 | |
| 118 | return false; // No backslash found (likely invalid input) |
| 119 | } |
| 120 | |
| 121 | static bool wel_add_to_registry(const wchar_t *channel, const wchar_t *provider, DWORD defaultMaxSize) { |
| 122 | // Build the registry path: SYSTEM\CurrentControlSet\Services\EventLog\<LogName>\<SourceName> |
| 123 | wchar_t key[MAX_PATH]; |
| 124 | if(!provider) |
| 125 | swprintf(key, MAX_PATH, L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\%ls", channel); |
| 126 | else |
| 127 | swprintf(key, MAX_PATH, L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\%ls\\%ls", channel, provider); |
| 128 | |
| 129 | HKEY hRegKey; |
| 130 | DWORD disposition; |
| 131 | LONG result = RegCreateKeyExW(HKEY_LOCAL_MACHINE, key, |
| 132 | 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hRegKey, &disposition); |
| 133 | |
| 134 | if (result != ERROR_SUCCESS) |
| 135 | return false; // Could not create the registry key |
| 136 | |
| 137 | // Check if MaxSize is already set |
| 138 | DWORD maxSize = 0; |
| 139 | DWORD size = sizeof(maxSize); |
| 140 | if (RegQueryValueExW(hRegKey, L"MaxSize", NULL, NULL, (LPBYTE)&maxSize, &size) != ERROR_SUCCESS) { |
| 141 | // MaxSize is not set, set it to the default value |
| 142 | RegSetValueExW(hRegKey, L"MaxSize", 0, REG_DWORD, (const BYTE*)&defaultMaxSize, sizeof(defaultMaxSize)); |
| 143 | } |
| 144 | |
| 145 | wchar_t modulePath[MAX_PATH]; |
| 146 | if (GetModuleFileNameW(NULL, modulePath, MAX_PATH) == 0) { |
| 147 | RegCloseKey(hRegKey); |
| 148 | return false; |
| 149 | } |
| 150 | |
| 151 | if(wel_replace_program_with_wevt_netdata_dll(modulePath, _countof(modulePath))) { |
| 152 | RegSetValueExW(hRegKey, L"EventMessageFile", 0, REG_EXPAND_SZ, |
| 153 | (LPBYTE)modulePath, (wcslen(modulePath) + 1) * sizeof(wchar_t)); |
| 154 | |
| 155 | DWORD types_supported = EVENTLOG_SUCCESS | EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE; |
| 156 | RegSetValueExW(hRegKey, L"TypesSupported", 0, REG_DWORD, (LPBYTE)&types_supported, sizeof(DWORD)); |
| 157 | } |
| 158 | |
| 159 | RegCloseKey(hRegKey); |
| 160 | return true; |
| 161 | } |
| 162 | |
| 163 | #if defined(HAVE_ETW) |
| 164 | static void etw_set_source_meta(struct nd_log_source *source, USHORT channelID, const EVENT_DESCRIPTOR *ed) { |
| 165 | // It turns out that the keyword varies per only per channel! |
| 166 | // so, to log with the right keyword, Task, Opcode we copy the ids from the header |
| 167 | // the messages compiler (mc.exe) generated from the manifest. |
| 168 | |
| 169 | source->channelID = channelID; |
| 170 | source->Opcode = ed->Opcode; |
| 171 | source->Task = ed->Task; |
| 172 | source->Keyword = ed->Keyword; |
| 173 | } |
| 174 | |
| 175 | // Callback for provider enable/disable notifications |
| 176 | static void NTAPI ProviderEnableCallback( |
| 177 | LPCGUID SourceId __maybe_unused, |
| 178 | ULONG IsEnabled, |
| 179 | UCHAR Level __maybe_unused, |
| 180 | ULONGLONG MatchAnyKeyword __maybe_unused, |
| 181 | ULONGLONG MatchAllKeyword __maybe_unused, |
| 182 | PEVENT_FILTER_DESCRIPTOR FilterData __maybe_unused, |
| 183 | PVOID CallbackContext __maybe_unused |
| 184 | ) { |
| 185 | spinlock_lock(&nd_log.eventlog.provider_lock); |
| 186 | nd_log.eventlog.provider_enabled = IsEnabled ? true : false; |
| 187 | spinlock_unlock(&nd_log.eventlog.provider_lock); |
| 188 | } |
| 189 | |
| 190 | static bool etw_register_provider(void) { |
| 191 | spinlock_init(&nd_log.eventlog.provider_lock); |
| 192 | nd_log.eventlog.provider_enabled = false; |
| 193 | |
| 194 | // Register the ETW provider |
| 195 | if (EventRegister(&NETDATA_ETW_PROVIDER_GUID, ProviderEnableCallback, NULL, ®Handle) != ERROR_SUCCESS) |
| 196 | return false; |
| 197 | |
| 198 | etw_set_source_meta(&nd_log.sources[NDLS_DAEMON], CHANNEL_DAEMON, &ED_DAEMON_INFO_MESSAGE_ONLY); |
| 199 | etw_set_source_meta(&nd_log.sources[NDLS_COLLECTORS], CHANNEL_COLLECTORS, &ED_COLLECTORS_INFO_MESSAGE_ONLY); |
| 200 | etw_set_source_meta(&nd_log.sources[NDLS_ACCESS], CHANNEL_ACCESS, &ED_ACCESS_INFO_MESSAGE_ONLY); |
| 201 | etw_set_source_meta(&nd_log.sources[NDLS_HEALTH], CHANNEL_HEALTH, &ED_HEALTH_INFO_MESSAGE_ONLY); |
| 202 | etw_set_source_meta(&nd_log.sources[NDLS_ACLK], CHANNEL_ACLK, &ED_ACLK_INFO_MESSAGE_ONLY); |
| 203 | etw_set_source_meta(&nd_log.sources[NDLS_UNSET], CHANNEL_DAEMON, &ED_DAEMON_INFO_MESSAGE_ONLY); |
| 204 | etw_set_source_meta(&nd_log.sources[NDLS_DEBUG], CHANNEL_DAEMON, &ED_DAEMON_INFO_MESSAGE_ONLY); |
| 205 | |
| 206 | DWORD wait_start = GetTickCount(); |
| 207 | while(true) { |
| 208 | spinlock_lock(&nd_log.eventlog.provider_lock); |
| 209 | bool enabled = nd_log.eventlog.provider_enabled; |
| 210 | spinlock_unlock(&nd_log.eventlog.provider_lock); |
| 211 | |
| 212 | if(enabled) |
| 213 | return true; |
| 214 | |
| 215 | // Timeout after 5 seconds |
| 216 | if(GetTickCount() - wait_start > 5000) { |
| 217 | EventUnregister(regHandle); |
| 218 | return false; |
| 219 | } |
| 220 | |
| 221 | Sleep(10); // Short sleep between checks |
| 222 | } |
| 223 | } |
| 224 | #endif |
| 225 | |
| 226 | bool nd_log_init_windows(void) { |
| 227 | if(nd_log.eventlog.initialized) |
| 228 | return true; |
| 229 | |
| 230 | // validate we have the right keys |
| 231 | if( |
| 232 | !check_event_id(NDLS_COLLECTORS, NDLP_INFO, MSGID_MESSAGE_ONLY, MC_COLLECTORS_INFO_MESSAGE_ONLY) || |
| 233 | !check_event_id(NDLS_DAEMON, NDLP_ERR, MSGID_MESSAGE_ONLY, MC_DAEMON_ERR_MESSAGE_ONLY) || |
| 234 | !check_event_id(NDLS_ACCESS, NDLP_WARNING, MSGID_ACCESS_USER, MC_ACCESS_WARN_ACCESS_USER) || |
| 235 | !check_event_id(NDLS_HEALTH, NDLP_CRIT, MSGID_ALERT_TRANSITION, MC_HEALTH_CRIT_ALERT_TRANSITION) || |
| 236 | !check_event_id(NDLS_DEBUG, NDLP_ALERT, MSGID_ACCESS_FORWARDER_USER, MC_DEBUG_ALERT_ACCESS_FORWARDER_USER)) |
| 237 | return false; |
| 238 | |
| 239 | #if defined(HAVE_ETW) |
| 240 | if(nd_log.eventlog.etw && !etw_register_provider()) |
| 241 | return false; |
| 242 | #endif |
| 243 | |
| 244 | // if(!nd_log.eventlog.etw && !wel_add_to_registry(NETDATA_WEL_CHANNEL_NAME_W, NULL, 50 * 1024 * 1024)) |
| 245 | // return false; |
| 246 | |
| 247 | // Loop through each source and add it to the registry |
| 248 | for(size_t i = 0; i < _NDLS_MAX; i++) { |
| 249 | nd_log.sources[i].source = i; |
| 250 | |
| 251 | const wchar_t *sub_channel = wel_provider_per_source[i]; |
| 252 | |
| 253 | if(!sub_channel) |
| 254 | // we will map these to NDLS_DAEMON |
| 255 | continue; |
| 256 | |
| 257 | DWORD defaultMaxSize = 0; |
| 258 | switch (i) { |
| 259 | case NDLS_ACLK: |
| 260 | defaultMaxSize = 5 * 1024 * 1024; |
| 261 | break; |
| 262 | |
| 263 | case NDLS_HEALTH: |
| 264 | defaultMaxSize = 35 * 1024 * 1024; |
| 265 | break; |
| 266 | |
| 267 | default: |
| 268 | case NDLS_ACCESS: |
| 269 | case NDLS_COLLECTORS: |
| 270 | case NDLS_DAEMON: |
| 271 | defaultMaxSize = 20 * 1024 * 1024; |
| 272 | break; |
| 273 | } |
| 274 | |
| 275 | if(!nd_log.eventlog.etw) { |
| 276 | if(!wel_add_to_registry(NETDATA_WEL_CHANNEL_NAME_W, sub_channel, defaultMaxSize)) |
| 277 | return false; |
| 278 | |
| 279 | // when not using a manifest, each source is a provider |
| 280 | nd_log.sources[i].hEventLog = RegisterEventSourceW(NULL, sub_channel); |
| 281 | if (!nd_log.sources[i].hEventLog) |
| 282 | return false; |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | if(!nd_log.eventlog.etw) { |
| 287 | // Map the unset ones to NDLS_DAEMON |
| 288 | for (size_t i = 0; i < _NDLS_MAX; i++) { |
| 289 | if (!nd_log.sources[i].hEventLog) |
| 290 | nd_log.sources[i].hEventLog = nd_log.sources[NDLS_DAEMON].hEventLog; |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | nd_log.eventlog.initialized = true; |
| 295 | return true; |
| 296 | } |
| 297 | |
| 298 | bool nd_log_init_etw(void) { |
| 299 | nd_log.eventlog.etw = true; |
| 300 | return nd_log_init_windows(); |
| 301 | } |
| 302 | |
| 303 | bool nd_log_init_wel(void) { |
| 304 | nd_log.eventlog.etw = false; |
| 305 | return nd_log_init_windows(); |
| 306 | } |
| 307 | |
| 308 | // -------------------------------------------------------------------------------------------------------------------- |
| 309 | // we pass all our fields to the windows events logs |
| 310 | // numbered the same way we have them in memory. |
| 311 | // |
| 312 | // to avoid runtime memory allocations, we use a static allocations with ready to use buffers |
| 313 | // which are immediately available for logging. |
| 314 | |
| 315 | #define SMALL_WIDE_BUFFERS_SIZE 256 |
| 316 | #define MEDIUM_WIDE_BUFFERS_SIZE 2048 |
| 317 | #define BIG_WIDE_BUFFERS_SIZE 16384 |
| 318 | static wchar_t small_wide_buffers[_NDF_MAX][SMALL_WIDE_BUFFERS_SIZE]; |
| 319 | static wchar_t medium_wide_buffers[2][MEDIUM_WIDE_BUFFERS_SIZE]; |
| 320 | static wchar_t big_wide_buffers[2][BIG_WIDE_BUFFERS_SIZE]; |
| 321 | |
| 322 | static struct { |
| 323 | size_t size; |
| 324 | wchar_t *buf; |
| 325 | } fields_buffers[_NDF_MAX] = { 0 }; |
| 326 | |
| 327 | #if defined(HAVE_ETW) |
| 328 | static EVENT_DATA_DESCRIPTOR etw_eventData[_NDF_MAX - 1]; |
| 329 | #endif |
| 330 | |
| 331 | static LPCWSTR wel_messages[_NDF_MAX - 1]; |
| 332 | |
| 333 | __attribute__((constructor)) void wevents_initialize_buffers(void) { |
| 334 | for(size_t i = 0; i < _NDF_MAX ;i++) { |
| 335 | fields_buffers[i].buf = small_wide_buffers[i]; |
| 336 | fields_buffers[i].size = SMALL_WIDE_BUFFERS_SIZE; |
| 337 | } |
| 338 | |
| 339 | fields_buffers[NDF_NIDL_INSTANCE].buf = medium_wide_buffers[0]; |
| 340 | fields_buffers[NDF_NIDL_INSTANCE].size = MEDIUM_WIDE_BUFFERS_SIZE; |
| 341 | |
| 342 | fields_buffers[NDF_REQUEST].buf = big_wide_buffers[0]; |
| 343 | fields_buffers[NDF_REQUEST].size = BIG_WIDE_BUFFERS_SIZE; |
| 344 | fields_buffers[NDF_MESSAGE].buf = big_wide_buffers[1]; |
| 345 | fields_buffers[NDF_MESSAGE].size = BIG_WIDE_BUFFERS_SIZE; |
| 346 | |
| 347 | for(size_t i = 1; i < _NDF_MAX ;i++) |
| 348 | wel_messages[i - 1] = fields_buffers[i].buf; |
| 349 | } |
| 350 | |
| 351 | // -------------------------------------------------------------------------------------------------------------------- |
| 352 | |
| 353 | #define is_field_set(fields, fields_max, field) ((field) < (fields_max) && (fields)[field].entry.set) |
| 354 | |
| 355 | static const char *get_field_value_unsafe(struct log_field *fields, ND_LOG_FIELD_ID i, size_t fields_max, BUFFER **tmp) { |
| 356 | if(!is_field_set(fields, fields_max, i) || !fields[i].eventlog) |
| 357 | return ""; |
| 358 | |
| 359 | static char number_str[MAX(MAX(UINT64_MAX_LENGTH, DOUBLE_MAX_LENGTH), UUID_STR_LEN)]; |
| 360 | |
| 361 | const char *s = NULL; |
| 362 | if (fields[i].logfmt_annotator) |
| 363 | s = fields[i].logfmt_annotator(&fields[i]); |
| 364 | |
| 365 | else |
| 366 | switch (fields[i].entry.type) { |
| 367 | case NDFT_TXT: |
| 368 | s = fields[i].entry.txt; |
| 369 | break; |
| 370 | case NDFT_STR: |
| 371 | s = string2str(fields[i].entry.str); |
| 372 | break; |
| 373 | case NDFT_BFR: |
| 374 | s = buffer_tostring(fields[i].entry.bfr); |
| 375 | break; |
| 376 | case NDFT_U64: |
| 377 | print_uint64(number_str, fields[i].entry.u64); |
| 378 | s = number_str; |
| 379 | break; |
| 380 | case NDFT_I64: |
| 381 | print_int64(number_str, fields[i].entry.i64); |
| 382 | s = number_str; |
| 383 | break; |
| 384 | case NDFT_DBL: |
| 385 | print_netdata_double(number_str, fields[i].entry.dbl); |
| 386 | s = number_str; |
| 387 | break; |
| 388 | case NDFT_UUID: |
| 389 | if (!uuid_is_null(*fields[i].entry.uuid)) { |
| 390 | uuid_unparse_lower_compact(*fields[i].entry.uuid, number_str); |
| 391 | s = number_str; |
| 392 | } |
| 393 | break; |
| 394 | case NDFT_CALLBACK: |
| 395 | if (!*tmp) |
| 396 | *tmp = buffer_create(1024, NULL); |
| 397 | else |
| 398 | buffer_flush(*tmp); |
| 399 | |
| 400 | if (fields[i].entry.cb.formatter(*tmp, fields[i].entry.cb.formatter_data)) |
| 401 | s = buffer_tostring(*tmp); |
| 402 | else |
| 403 | s = NULL; |
| 404 | break; |
| 405 | |
| 406 | default: |
| 407 | s = "UNHANDLED"; |
| 408 | break; |
| 409 | } |
| 410 | |
| 411 | if(!s || !*s) return ""; |
| 412 | return s; |
| 413 | } |
| 414 | static void etw_replace_percent_with_unicode(wchar_t *s, size_t size) { |
| 415 | size_t original_len = wcslen(s); |
| 416 | |
| 417 | // Traverse the string, replacing '%' with the Unicode fullwidth percent sign |
| 418 | for (size_t i = 0; i < original_len && i < size - 1; i++) { |
| 419 | if (s[i] == L'%' && iswdigit(s[i + 1])) { |
| 420 | // s[i] = 0xFF05; // Replace '%' with fullwidth percent sign '%' |
| 421 | // s[i] = 0x29BC; // ⦼ |
| 422 | s[i] = 0x2105; // ℅ |
| 423 | } |
| 424 | } |
| 425 | |
| 426 | // Ensure null termination if needed |
| 427 | s[size - 1] = L'\0'; |
| 428 | } |
| 429 | |
| 430 | static void wevt_generate_all_fields_unsafe(struct log_field *fields, size_t fields_max, BUFFER **tmp) { |
| 431 | for (size_t i = 0; i < fields_max; i++) { |
| 432 | fields_buffers[i].buf[0] = L'\0'; |
| 433 | |
| 434 | if (!fields[i].entry.set || !fields[i].eventlog) |
| 435 | continue; |
| 436 | |
| 437 | const char *s = get_field_value_unsafe(fields, i, fields_max, tmp); |
| 438 | if (s && *s) { |
| 439 | utf8_to_utf16(fields_buffers[i].buf, (int) fields_buffers[i].size, s, -1); |
| 440 | |
| 441 | if(nd_log.eventlog.etw) |
| 442 | // UNBELIEVABLE! they do recursive parameter expansion in ETW... |
| 443 | etw_replace_percent_with_unicode(fields_buffers[i].buf, fields_buffers[i].size); |
| 444 | } |
| 445 | } |
| 446 | } |
| 447 | |
| 448 | static bool has_user_role_permissions(struct log_field *fields, size_t fields_max, BUFFER **tmp) { |
| 449 | const char *t; |
| 450 | |
| 451 | t = get_field_value_unsafe(fields, NDF_USER_NAME, fields_max, tmp); |
| 452 | if (*t) return true; |
| 453 | |
| 454 | t = get_field_value_unsafe(fields, NDF_USER_ROLE, fields_max, tmp); |
| 455 | if (*t && strcmp(t, "none") != 0) return true; |
| 456 | |
| 457 | t = get_field_value_unsafe(fields, NDF_USER_ACCESS, fields_max, tmp); |
| 458 | if (*t && strcmp(t, "0x0") != 0) return true; |
| 459 | |
| 460 | return false; |
| 461 | } |
| 462 | |
| 463 | static bool nd_logger_windows(struct nd_log_source *source, struct log_field *fields, size_t fields_max) { |
| 464 | if (!nd_log.eventlog.initialized) |
| 465 | return false; |
| 466 | |
| 467 | ND_LOG_FIELD_PRIORITY priority = NDLP_INFO; |
| 468 | if (fields[NDF_PRIORITY].entry.set) |
| 469 | priority = (ND_LOG_FIELD_PRIORITY) fields[NDF_PRIORITY].entry.u64; |
| 470 | |
| 471 | DWORD wType = get_event_type_from_priority(priority); |
| 472 | (void) wType; |
| 473 | |
| 474 | CLEAN_BUFFER *tmp = NULL; |
| 475 | |
| 476 | static SPINLOCK spinlock = SPINLOCK_INITIALIZER; |
| 477 | spinlock_lock(&spinlock); |
| 478 | wevt_generate_all_fields_unsafe(fields, fields_max, &tmp); |
| 479 | |
| 480 | MESSAGE_ID messageID; |
| 481 | switch (source->source) { |
| 482 | default: |
| 483 | case NDLS_DEBUG: |
| 484 | case NDLS_DAEMON: |
| 485 | case NDLS_COLLECTORS: |
| 486 | messageID = MSGID_MESSAGE_ONLY; |
| 487 | break; |
| 488 | |
| 489 | case NDLS_HEALTH: |
| 490 | messageID = MSGID_ALERT_TRANSITION; |
| 491 | break; |
| 492 | |
| 493 | case NDLS_ACCESS: |
| 494 | if (is_field_set(fields, fields_max, NDF_MESSAGE)) { |
| 495 | messageID = MSGID_ACCESS_MESSAGE; |
| 496 | |
| 497 | if (has_user_role_permissions(fields, fields_max, &tmp)) |
| 498 | messageID = MSGID_ACCESS_MESSAGE_USER; |
| 499 | else if (*get_field_value_unsafe(fields, NDF_REQUEST, fields_max, &tmp)) |
| 500 | messageID = MSGID_ACCESS_MESSAGE_REQUEST; |
| 501 | } else if (is_field_set(fields, fields_max, NDF_RESPONSE_CODE)) { |
| 502 | messageID = MSGID_ACCESS; |
| 503 | |
| 504 | if (*get_field_value_unsafe(fields, NDF_SRC_FORWARDED_FOR, fields_max, &tmp)) |
| 505 | messageID = MSGID_ACCESS_FORWARDER; |
| 506 | |
| 507 | if (has_user_role_permissions(fields, fields_max, &tmp)) { |
| 508 | if (messageID == MSGID_ACCESS) |
| 509 | messageID = MSGID_ACCESS_USER; |
| 510 | else |
| 511 | messageID = MSGID_ACCESS_FORWARDER_USER; |
| 512 | } |
| 513 | } else |
| 514 | messageID = MSGID_REQUEST_ONLY; |
| 515 | break; |
| 516 | |
| 517 | case NDLS_ACLK: |
| 518 | messageID = MSGID_MESSAGE_ONLY; |
| 519 | break; |
| 520 | } |
| 521 | |
| 522 | if (messageID == MSGID_MESSAGE_ONLY && ( |
| 523 | *get_field_value_unsafe(fields, NDF_ERRNO, fields_max, &tmp) || |
| 524 | *get_field_value_unsafe(fields, NDF_WINERROR, fields_max, &tmp))) { |
| 525 | messageID = MSGID_MESSAGE_ERRNO; |
| 526 | } |
| 527 | |
| 528 | DWORD eventID = construct_event_id(source->source, priority, messageID); |
| 529 | |
| 530 | // wType |
| 531 | // |
| 532 | // without a manifest => this determines the Level of the event |
| 533 | // with a manifest => Level from the manifest is used (wType ignored) |
| 534 | // [however it is good to have, in case the manifest is not accessible somehow] |
| 535 | // |
| 536 | |
| 537 | // wCategory |
| 538 | // |
| 539 | // without a manifest => numeric Task values appear |
| 540 | // with a manifest => Task from the manifest is used (wCategory ignored) |
| 541 | |
| 542 | BOOL rc; |
| 543 | #if defined(HAVE_ETW) |
| 544 | if (nd_log.eventlog.etw) { |
| 545 | // metadata based logging - ETW |
| 546 | |
| 547 | for (size_t i = 1; i < _NDF_MAX; i++) |
| 548 | EventDataDescCreate(&etw_eventData[i - 1], fields_buffers[i].buf, |
| 549 | (wcslen(fields_buffers[i].buf) + 1) * sizeof(WCHAR)); |
| 550 | |
| 551 | EVENT_DESCRIPTOR EventDesc = { |
| 552 | .Id = eventID & EVENT_ID_CODE_MASK, // ETW needs the raw event id |
| 553 | .Version = 0, |
| 554 | .Channel = source->channelID, |
| 555 | .Level = get_level_from_priority(priority), |
| 556 | .Opcode = source->Opcode, |
| 557 | .Task = source->Task, |
| 558 | .Keyword = source->Keyword, |
| 559 | }; |
| 560 | |
| 561 | rc = ERROR_SUCCESS == EventWrite(regHandle, &EventDesc, _NDF_MAX - 1, etw_eventData); |
| 562 | |
| 563 | } |
| 564 | else |
| 565 | #endif |
| 566 | { |
| 567 | // eventID based logging - WEL |
| 568 | rc = ReportEventW(source->hEventLog, wType, 0, eventID, NULL, _NDF_MAX - 1, 0, wel_messages, NULL); |
| 569 | } |
| 570 | |
| 571 | spinlock_unlock(&spinlock); |
| 572 | |
| 573 | return rc == TRUE; |
| 574 | } |
| 575 | |
| 576 | #if defined(HAVE_ETW) |
| 577 | bool nd_logger_etw(struct nd_log_source *source, struct log_field *fields, size_t fields_max) { |
| 578 | return nd_logger_windows(source, fields, fields_max); |
| 579 | } |
| 580 | #endif |
| 581 | |
| 582 | #if defined(HAVE_WEL) |
| 583 | bool nd_logger_wel(struct nd_log_source *source, struct log_field *fields, size_t fields_max) { |
| 584 | return nd_logger_windows(source, fields, fields_max); |
| 585 | } |
| 586 | #endif |
| 587 | |
| 588 | #endif |