winevt_c 0.11.5 → 0.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +2 -2
- data/README.md +1 -1
- data/ext/winevt/extconf.rb +0 -3
- data/ext/winevt/winevt_bookmark.c +8 -4
- data/ext/winevt/winevt_c.h +4 -6
- data/ext/winevt/winevt_channel.c +26 -24
- data/ext/winevt/winevt_locale.c +1 -1
- data/ext/winevt/winevt_query.c +100 -13
- data/ext/winevt/winevt_session.c +18 -4
- data/ext/winevt/winevt_subscribe.c +51 -13
- data/ext/winevt/winevt_utils.cpp +109 -148
- data/lib/winevt/version.rb +1 -1
- data/winevt_c.gemspec +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8cae317f8e53bccce324f256e88df86042e221489c05384728ceb44acf40929d
|
|
4
|
+
data.tar.gz: a08a75d868d7ff8f186077f0dbaafc3fd82ef6a89f8ebd92c8deeaab996fa257
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3ca5a2836e88d7e1815a04d4f3698c4526d64643035833f7ce22acc220de23d7c5f26222f398284da02f47d55b36fd0f262124a2c38cfe8daff057813b919374
|
|
7
|
+
data.tar.gz: 426256cdc15034f4acfadcac742f854467b8eb574efe1f15e6cb2b8e8b6ac8d4b3fc70644953d4382b55fd7378f98b88bd51628d957a7200973fb24ac400f5a3
|
data/.github/workflows/test.yml
CHANGED
|
@@ -28,8 +28,8 @@ jobs:
|
|
|
28
28
|
ruby: '3.2'
|
|
29
29
|
name: Ruby ${{ matrix.ruby }} on ${{ matrix.os }}
|
|
30
30
|
steps:
|
|
31
|
-
- uses: actions/checkout@
|
|
32
|
-
- uses: ruby/setup-ruby@
|
|
31
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
32
|
+
- uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
|
|
33
33
|
with:
|
|
34
34
|
ruby-version: ${{ matrix.ruby }}
|
|
35
35
|
- name: Run tests
|
data/README.md
CHANGED
data/ext/winevt/extconf.rb
CHANGED
|
@@ -14,9 +14,6 @@ have_library("wevtapi")
|
|
|
14
14
|
have_func("EvtQuery", "winevt.h")
|
|
15
15
|
have_library("advapi32")
|
|
16
16
|
have_library("ole32")
|
|
17
|
-
if have_macro("RB_ALLOCV")
|
|
18
|
-
$CFLAGS << " -DHAVE_RB_ALLOCV=1 "
|
|
19
|
-
end
|
|
20
17
|
|
|
21
18
|
$LDFLAGS << " -lwevtapi -ladvapi32 -lole32"
|
|
22
19
|
$CFLAGS << " -Wall -std=c99 -fPIC -fms-extensions "
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* puts @bookmark.render
|
|
19
19
|
*/
|
|
20
|
-
/* clang-format
|
|
20
|
+
/* clang-format on */
|
|
21
21
|
|
|
22
22
|
VALUE rb_cBookmark;
|
|
23
23
|
|
|
@@ -54,9 +54,9 @@ rb_winevt_bookmark_alloc(VALUE klass)
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/*
|
|
57
|
-
*
|
|
57
|
+
* Initialize Bookmark class. Receive XML string or nil.
|
|
58
58
|
*
|
|
59
|
-
* @overload
|
|
59
|
+
* @overload initialize(options={})
|
|
60
60
|
* @option options [String] XML rendered Bookmark string.
|
|
61
61
|
* @return [Bookmark]
|
|
62
62
|
*
|
|
@@ -109,12 +109,16 @@ rb_winevt_bookmark_update(VALUE self, VALUE event)
|
|
|
109
109
|
struct WinevtQuery* winevtQuery;
|
|
110
110
|
struct WinevtBookmark* winevtBookmark;
|
|
111
111
|
|
|
112
|
+
if (!rb_obj_is_kind_of(event, rb_cQuery)) {
|
|
113
|
+
rb_raise(rb_eArgError, "Expected a Query instance");
|
|
114
|
+
}
|
|
115
|
+
|
|
112
116
|
winevtQuery = EventQuery(event);
|
|
113
117
|
|
|
114
118
|
TypedData_Get_Struct(
|
|
115
119
|
self, struct WinevtBookmark, &rb_winevt_bookmark_type, winevtBookmark);
|
|
116
120
|
|
|
117
|
-
for (
|
|
121
|
+
for (ULONG i = 0; i < winevtQuery->count; i++) {
|
|
118
122
|
if (!EvtUpdateBookmark(winevtBookmark->bookmark, winevtQuery->hEvents[i]))
|
|
119
123
|
return Qfalse;
|
|
120
124
|
}
|
data/ext/winevt/winevt_c.h
CHANGED
|
@@ -16,11 +16,6 @@
|
|
|
16
16
|
#endif /* WIN32_WINNT */
|
|
17
17
|
#define _WIN32_WINNT MINIMUM_WINDOWS_VERSION
|
|
18
18
|
|
|
19
|
-
#if !defined(HAVE_RB_ALLOCV)
|
|
20
|
-
#define ALLOCV RB_ALLOCV
|
|
21
|
-
#define ALLOCV_N RB_ALLOCV_N
|
|
22
|
-
#endif
|
|
23
|
-
|
|
24
19
|
#include <time.h>
|
|
25
20
|
#include <winevt.h>
|
|
26
21
|
#define EventQuery(object) ((struct WinevtQuery*)DATA_PTR(object))
|
|
@@ -52,7 +47,8 @@ EVT_HANDLE connect_to_remote(LPWSTR computerName, LPWSTR domain,
|
|
|
52
47
|
LPWSTR username, LPWSTR password,
|
|
53
48
|
EVT_RPC_LOGIN_FLAGS flags,
|
|
54
49
|
DWORD *error_code);
|
|
55
|
-
WCHAR* get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote
|
|
50
|
+
WCHAR* get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote,
|
|
51
|
+
BOOL* resolved);
|
|
56
52
|
VALUE get_values(EVT_HANDLE handle);
|
|
57
53
|
VALUE render_system_event(EVT_HANDLE handle, BOOL preserve_qualifiers, BOOL preserveSID);
|
|
58
54
|
LocaleInfo* get_locale_info_from_rb_str(VALUE rb_locale_str);
|
|
@@ -112,6 +108,7 @@ struct WinevtQuery
|
|
|
112
108
|
BOOL preserveSID;
|
|
113
109
|
LocaleInfo *localeInfo;
|
|
114
110
|
EVT_HANDLE remoteHandle;
|
|
111
|
+
ULONGLONG unresolvedMessageCount;
|
|
115
112
|
};
|
|
116
113
|
|
|
117
114
|
#define SUBSCRIBE_ARRAY_SIZE 10
|
|
@@ -134,6 +131,7 @@ struct WinevtSubscribe
|
|
|
134
131
|
BOOL preserveSID;
|
|
135
132
|
LocaleInfo* localeInfo;
|
|
136
133
|
EVT_HANDLE remoteHandle;
|
|
134
|
+
ULONGLONG unresolvedMessageCount;
|
|
137
135
|
};
|
|
138
136
|
|
|
139
137
|
void Init_winevt_query(VALUE rb_cEventLog);
|
data/ext/winevt/winevt_channel.c
CHANGED
|
@@ -56,7 +56,7 @@ rb_winevt_channel_alloc(VALUE klass)
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/*
|
|
59
|
-
*
|
|
59
|
+
* Initialize Channel class.
|
|
60
60
|
*
|
|
61
61
|
* @return [Channel]
|
|
62
62
|
*
|
|
@@ -177,6 +177,14 @@ DWORD check_subscribable_with_channel_config_type(int Id, PEVT_VARIANT pProperty
|
|
|
177
177
|
#undef EVENT_DEBUG_TYPE
|
|
178
178
|
#undef EVENT_ANALYTICAL_TYPE
|
|
179
179
|
|
|
180
|
+
// A per-channel failure skips that channel, but memory exhaustion is not a
|
|
181
|
+
// property of any channel and would silently shorten the whole list.
|
|
182
|
+
static BOOL
|
|
183
|
+
is_resource_exhaustion(DWORD status)
|
|
184
|
+
{
|
|
185
|
+
return status == ERROR_OUTOFMEMORY || status == ERROR_NOT_ENOUGH_MEMORY;
|
|
186
|
+
}
|
|
187
|
+
|
|
180
188
|
/*
|
|
181
189
|
* Enumerate Windows EventLog channels
|
|
182
190
|
*
|
|
@@ -245,34 +253,26 @@ rb_winevt_channel_each(VALUE self)
|
|
|
245
253
|
}
|
|
246
254
|
hChannelConfig = EvtOpenChannelConfig(NULL, buffer, 0);
|
|
247
255
|
if (NULL == hChannelConfig) {
|
|
248
|
-
|
|
249
|
-
_countof(errBuf),
|
|
250
|
-
_TRUNCATE,
|
|
251
|
-
"EvtOpenChannelConfig failed with %lu.\n",
|
|
252
|
-
GetLastError());
|
|
253
|
-
|
|
254
|
-
EvtClose(winevtChannel->channels);
|
|
255
|
-
winevtChannel->channels = NULL;
|
|
256
|
+
status = GetLastError();
|
|
256
257
|
|
|
257
258
|
free(buffer);
|
|
258
259
|
buffer = NULL;
|
|
259
260
|
bufferSize = 0;
|
|
260
261
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
status = is_subscribable_channel_p(hChannelConfig, winevtChannel->force_enumerate);
|
|
265
|
-
EvtClose(hChannelConfig);
|
|
262
|
+
if (is_resource_exhaustion(status)) {
|
|
263
|
+
EvtClose(winevtChannel->channels);
|
|
264
|
+
winevtChannel->channels = NULL;
|
|
266
265
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
buffer = NULL;
|
|
270
|
-
bufferSize = 0;
|
|
266
|
+
rb_raise(rb_eRuntimeError, "EvtOpenChannelConfig failed with %lu\n", status);
|
|
267
|
+
}
|
|
271
268
|
|
|
272
269
|
continue;
|
|
273
270
|
}
|
|
274
271
|
|
|
275
|
-
|
|
272
|
+
status = is_subscribable_channel_p(hChannelConfig, winevtChannel->force_enumerate);
|
|
273
|
+
EvtClose(hChannelConfig);
|
|
274
|
+
|
|
275
|
+
if (is_resource_exhaustion(status)) {
|
|
276
276
|
EvtClose(winevtChannel->channels);
|
|
277
277
|
winevtChannel->channels = NULL;
|
|
278
278
|
|
|
@@ -280,16 +280,18 @@ rb_winevt_channel_each(VALUE self)
|
|
|
280
280
|
buffer = NULL;
|
|
281
281
|
bufferSize = 0;
|
|
282
282
|
|
|
283
|
-
rb_raise(rb_eRuntimeError, "
|
|
284
|
-
}
|
|
285
|
-
EvtClose(winevtChannel->channels);
|
|
286
|
-
winevtChannel->channels = NULL;
|
|
283
|
+
rb_raise(rb_eRuntimeError, "Failed to read channel configuration with %lu\n", status);
|
|
284
|
+
}
|
|
287
285
|
|
|
286
|
+
// Not fatal: Security's configuration cannot be read without elevation, and
|
|
287
|
+
// it is the third channel EvtOpenChannelEnum returns, so raising here
|
|
288
|
+
// truncated the whole enumeration to two channels.
|
|
289
|
+
if (status != ERROR_SUCCESS) {
|
|
288
290
|
free(buffer);
|
|
289
291
|
buffer = NULL;
|
|
290
292
|
bufferSize = 0;
|
|
291
293
|
|
|
292
|
-
|
|
294
|
+
continue;
|
|
293
295
|
}
|
|
294
296
|
|
|
295
297
|
utf8str = wstr_to_rb_str(CP_UTF8, buffer, -1);
|
data/ext/winevt/winevt_locale.c
CHANGED
data/ext/winevt/winevt_query.c
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#include <winevt_c.h>
|
|
2
2
|
|
|
3
|
+
#include <ruby/thread.h>
|
|
4
|
+
|
|
3
5
|
/* clang-format off */
|
|
4
6
|
/*
|
|
5
7
|
* Document-class: Winevt::EventLog::Query
|
|
@@ -39,7 +41,7 @@ close_handles(struct WinevtQuery* winevtQuery)
|
|
|
39
41
|
winevtQuery->query = NULL;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
for (
|
|
44
|
+
for (ULONG i = 0; i < winevtQuery->count; i++) {
|
|
43
45
|
if (winevtQuery->hEvents[i]) {
|
|
44
46
|
EvtClose(winevtQuery->hEvents[i]);
|
|
45
47
|
winevtQuery->hEvents[i] = NULL;
|
|
@@ -71,8 +73,35 @@ rb_winevt_query_alloc(VALUE klass)
|
|
|
71
73
|
return obj;
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
struct query_args
|
|
77
|
+
{
|
|
78
|
+
EVT_HANDLE session;
|
|
79
|
+
LPCWSTR channel;
|
|
80
|
+
LPCWSTR xpath;
|
|
81
|
+
DWORD flags;
|
|
82
|
+
EVT_HANDLE handle;
|
|
83
|
+
DWORD error_code;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/*
|
|
87
|
+
* GetLastError is read here rather than by the caller: it belongs to the thread
|
|
88
|
+
* that made the call and does not survive re-acquiring the GVL.
|
|
89
|
+
*/
|
|
90
|
+
static void*
|
|
91
|
+
query_without_gvl(void* ptr)
|
|
92
|
+
{
|
|
93
|
+
struct query_args* args = (struct query_args*)ptr;
|
|
94
|
+
|
|
95
|
+
args->handle = EvtQuery(args->session, args->channel, args->xpath, args->flags);
|
|
96
|
+
if (!args->handle) {
|
|
97
|
+
args->error_code = GetLastError();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return NULL;
|
|
101
|
+
}
|
|
102
|
+
|
|
74
103
|
/*
|
|
75
|
-
*
|
|
104
|
+
* Initialize Query class.
|
|
76
105
|
*
|
|
77
106
|
* @overload initialize(channel, xpath, session=nil)
|
|
78
107
|
* @param channel [String] Querying EventLog channel.
|
|
@@ -141,10 +170,30 @@ rb_winevt_query_initialize(VALUE argc, VALUE *argv, VALUE self)
|
|
|
141
170
|
|
|
142
171
|
TypedData_Get_Struct(self, struct WinevtQuery, &rb_winevt_query_type, winevtQuery);
|
|
143
172
|
|
|
144
|
-
|
|
145
|
-
|
|
173
|
+
if (hRemoteHandle != NULL) {
|
|
174
|
+
struct query_args args;
|
|
175
|
+
|
|
176
|
+
args.session = hRemoteHandle;
|
|
177
|
+
args.channel = evtChannel;
|
|
178
|
+
args.xpath = evtXPath;
|
|
179
|
+
args.flags = flags;
|
|
180
|
+
args.handle = NULL;
|
|
181
|
+
args.error_code = ERROR_SUCCESS;
|
|
182
|
+
|
|
183
|
+
// EvtOpenSession only builds the session object; this is the first call
|
|
184
|
+
// that reaches the remote machine, so it is the one that can stall.
|
|
185
|
+
rb_thread_call_without_gvl(query_without_gvl, &args, NULL, NULL);
|
|
186
|
+
|
|
187
|
+
winevtQuery->query = args.handle;
|
|
188
|
+
err = args.error_code;
|
|
189
|
+
} else {
|
|
190
|
+
winevtQuery->query = EvtQuery(hRemoteHandle, evtChannel, evtXPath, flags);
|
|
191
|
+
if (winevtQuery->query == NULL) {
|
|
192
|
+
err = GetLastError();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
146
196
|
if (winevtQuery->query == NULL) {
|
|
147
|
-
err = GetLastError();
|
|
148
197
|
if (hRemoteHandle != NULL) {
|
|
149
198
|
EvtClose(hRemoteHandle);
|
|
150
199
|
}
|
|
@@ -160,6 +209,7 @@ rb_winevt_query_initialize(VALUE argc, VALUE *argv, VALUE self)
|
|
|
160
209
|
winevtQuery->localeInfo = &default_locale;
|
|
161
210
|
winevtQuery->remoteHandle = hRemoteHandle;
|
|
162
211
|
winevtQuery->preserveSID = TRUE;
|
|
212
|
+
winevtQuery->unresolvedMessageCount = 0;
|
|
163
213
|
|
|
164
214
|
ALLOCV_END(wchannelBuf);
|
|
165
215
|
ALLOCV_END(wpathBuf);
|
|
@@ -260,8 +310,17 @@ rb_winevt_query_next(VALUE self)
|
|
|
260
310
|
}
|
|
261
311
|
|
|
262
312
|
if (status == ERROR_SUCCESS) {
|
|
313
|
+
// #each frees each batch in its ensure block; this covers calling #next
|
|
314
|
+
// directly in a loop.
|
|
315
|
+
for (ULONG i = 0; i < winevtQuery->count; i++) {
|
|
316
|
+
if (winevtQuery->hEvents[i]) {
|
|
317
|
+
EvtClose(winevtQuery->hEvents[i]);
|
|
318
|
+
winevtQuery->hEvents[i] = NULL;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
263
322
|
winevtQuery->count = count;
|
|
264
|
-
for (
|
|
323
|
+
for (ULONG i = 0; i < count; i++) {
|
|
265
324
|
winevtQuery->hEvents[i] = hEvents[i];
|
|
266
325
|
}
|
|
267
326
|
|
|
@@ -287,12 +346,19 @@ rb_winevt_query_render(VALUE self, EVT_HANDLE event)
|
|
|
287
346
|
}
|
|
288
347
|
|
|
289
348
|
static VALUE
|
|
290
|
-
rb_winevt_query_message(
|
|
349
|
+
rb_winevt_query_message(struct WinevtQuery* winevtQuery, EVT_HANDLE event)
|
|
291
350
|
{
|
|
292
351
|
WCHAR* wResult;
|
|
293
352
|
VALUE utf8str;
|
|
294
|
-
|
|
295
|
-
|
|
353
|
+
BOOL resolved = TRUE;
|
|
354
|
+
|
|
355
|
+
wResult = get_description(event,
|
|
356
|
+
winevtQuery->localeInfo->langID,
|
|
357
|
+
winevtQuery->remoteHandle,
|
|
358
|
+
&resolved);
|
|
359
|
+
if (!resolved) {
|
|
360
|
+
winevtQuery->unresolvedMessageCount++;
|
|
361
|
+
}
|
|
296
362
|
utf8str = wstr_to_rb_str(CP_UTF8, wResult, -1);
|
|
297
363
|
free(wResult);
|
|
298
364
|
|
|
@@ -382,7 +448,7 @@ rb_winevt_query_close_handle(VALUE self)
|
|
|
382
448
|
|
|
383
449
|
TypedData_Get_Struct(self, struct WinevtQuery, &rb_winevt_query_type, winevtQuery);
|
|
384
450
|
|
|
385
|
-
for (
|
|
451
|
+
for (ULONG i = 0; i < winevtQuery->count; i++) {
|
|
386
452
|
if (winevtQuery->hEvents[i] != NULL) {
|
|
387
453
|
EvtClose(winevtQuery->hEvents[i]);
|
|
388
454
|
winevtQuery->hEvents[i] = NULL;
|
|
@@ -401,11 +467,10 @@ rb_winevt_query_each_yield(VALUE self)
|
|
|
401
467
|
|
|
402
468
|
TypedData_Get_Struct(self, struct WinevtQuery, &rb_winevt_query_type, winevtQuery);
|
|
403
469
|
|
|
404
|
-
for (
|
|
470
|
+
for (ULONG i = 0; i < winevtQuery->count; i++) {
|
|
405
471
|
rb_yield_values(3,
|
|
406
472
|
rb_winevt_query_render(self, winevtQuery->hEvents[i]),
|
|
407
|
-
rb_winevt_query_message(winevtQuery->hEvents[i],
|
|
408
|
-
winevtQuery->remoteHandle),
|
|
473
|
+
rb_winevt_query_message(winevtQuery, winevtQuery->hEvents[i]),
|
|
409
474
|
rb_winevt_query_string_inserts(winevtQuery->hEvents[i]));
|
|
410
475
|
}
|
|
411
476
|
return Qnil;
|
|
@@ -577,6 +642,27 @@ rb_winevt_query_preserve_sid_p(VALUE self)
|
|
|
577
642
|
return winevtQuery->preserveSID ? Qtrue : Qfalse;
|
|
578
643
|
}
|
|
579
644
|
|
|
645
|
+
/*
|
|
646
|
+
* This method returns how many events had no description message. Message
|
|
647
|
+
* resolution fails for reasons outside the caller's control (a provider whose
|
|
648
|
+
* message DLL is gone, a locale without resources), and #each degrades to an
|
|
649
|
+
* empty message instead of aborting the enumeration. The counter increases
|
|
650
|
+
* monotonically, so callers can poll it and take the difference to tell a few
|
|
651
|
+
* unresolvable events apart from a channel that resolves nothing.
|
|
652
|
+
*
|
|
653
|
+
* @return [Integer]
|
|
654
|
+
*/
|
|
655
|
+
static VALUE
|
|
656
|
+
rb_winevt_query_get_unresolved_message_count(VALUE self)
|
|
657
|
+
{
|
|
658
|
+
struct WinevtQuery* winevtQuery;
|
|
659
|
+
|
|
660
|
+
TypedData_Get_Struct(
|
|
661
|
+
self, struct WinevtQuery, &rb_winevt_query_type, winevtQuery);
|
|
662
|
+
|
|
663
|
+
return ULL2NUM(winevtQuery->unresolvedMessageCount);
|
|
664
|
+
}
|
|
665
|
+
|
|
580
666
|
/*
|
|
581
667
|
* This method cancels channel query.
|
|
582
668
|
*
|
|
@@ -733,6 +819,7 @@ Init_winevt_query(VALUE rb_cEventLog)
|
|
|
733
819
|
* @since 0.11.0
|
|
734
820
|
*/
|
|
735
821
|
rb_define_method(rb_cQuery, "preserve_sid=", rb_winevt_query_set_preserve_sid, 1);
|
|
822
|
+
rb_define_method(rb_cQuery, "unresolved_message_count", rb_winevt_query_get_unresolved_message_count, 0);
|
|
736
823
|
/*
|
|
737
824
|
* @since 0.9.1
|
|
738
825
|
*/
|
data/ext/winevt/winevt_session.c
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* @session.domain = "<EXAMPLEGROUP>"
|
|
15
15
|
* @session.username = "<username>"
|
|
16
16
|
* @session.password = "<password>"
|
|
17
|
-
* # Then pass @session
|
|
17
|
+
* # Then pass @session variable into Winevt::EventLog::Query or
|
|
18
18
|
* # Winevt::EventLog::Subscribe#subscribe
|
|
19
19
|
* @query = Winevt::EventLog::Query.new(
|
|
20
20
|
* "Application",
|
|
@@ -60,8 +60,11 @@ session_free(void* ptr)
|
|
|
60
60
|
free(winevtSession->domain);
|
|
61
61
|
if (winevtSession->username)
|
|
62
62
|
free(winevtSession->username);
|
|
63
|
-
if (winevtSession->password)
|
|
63
|
+
if (winevtSession->password) {
|
|
64
|
+
SecureZeroMemory(winevtSession->password,
|
|
65
|
+
(wcslen(winevtSession->password) + 1) * sizeof(WCHAR));
|
|
64
66
|
free(winevtSession->password);
|
|
67
|
+
}
|
|
65
68
|
|
|
66
69
|
xfree(ptr);
|
|
67
70
|
}
|
|
@@ -77,7 +80,7 @@ rb_winevt_session_alloc(VALUE klass)
|
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
/*
|
|
80
|
-
*
|
|
83
|
+
* Initialize Session class.
|
|
81
84
|
*
|
|
82
85
|
* @overload initialize(server, domain=nil, username=nil, password=nil, flags=Winevt::EventLog::Session::RpcLoginFlag::AuthDefault)
|
|
83
86
|
* @param server [String] Server ip address or fqdn.
|
|
@@ -151,6 +154,8 @@ rb_winevt_session_set_server(VALUE self, VALUE rb_server)
|
|
|
151
154
|
RSTRING_PTR(rb_server), RSTRING_LEN(rb_server),
|
|
152
155
|
wServer, len);
|
|
153
156
|
wServer[len] = L'\0';
|
|
157
|
+
if (winevtSession->server)
|
|
158
|
+
free(winevtSession->server);
|
|
154
159
|
winevtSession->server = _wcsdup(wServer);
|
|
155
160
|
|
|
156
161
|
ALLOCV_END(vserverBuf);
|
|
@@ -204,6 +209,8 @@ rb_winevt_session_set_domain(VALUE self, VALUE rb_domain)
|
|
|
204
209
|
wDomain, len);
|
|
205
210
|
wDomain[len] = L'\0';
|
|
206
211
|
|
|
212
|
+
if (winevtSession->domain)
|
|
213
|
+
free(winevtSession->domain);
|
|
207
214
|
winevtSession->domain = _wcsdup(wDomain);
|
|
208
215
|
|
|
209
216
|
ALLOCV_END(vdomainBuf);
|
|
@@ -257,6 +264,8 @@ rb_winevt_session_set_username(VALUE self, VALUE rb_username)
|
|
|
257
264
|
wUsername, len);
|
|
258
265
|
wUsername[len] = L'\0';
|
|
259
266
|
|
|
267
|
+
if (winevtSession->username)
|
|
268
|
+
free(winevtSession->username);
|
|
260
269
|
winevtSession->username = _wcsdup(wUsername);
|
|
261
270
|
|
|
262
271
|
ALLOCV_END(vusernameBuf);
|
|
@@ -310,6 +319,11 @@ rb_winevt_session_set_password(VALUE self, VALUE rb_password)
|
|
|
310
319
|
wPassword, len);
|
|
311
320
|
wPassword[len] = L'\0';
|
|
312
321
|
|
|
322
|
+
if (winevtSession->password) {
|
|
323
|
+
SecureZeroMemory(winevtSession->password,
|
|
324
|
+
(wcslen(winevtSession->password) + 1) * sizeof(WCHAR));
|
|
325
|
+
free(winevtSession->password);
|
|
326
|
+
}
|
|
313
327
|
winevtSession->password = _wcsdup(wPassword);
|
|
314
328
|
|
|
315
329
|
ALLOCV_END(vpasswordBuf);
|
|
@@ -408,7 +422,7 @@ Init_winevt_session(VALUE rb_cEventLog)
|
|
|
408
422
|
*/
|
|
409
423
|
rb_define_const(rb_cRpcLoginFlag, "AuthDefault", LONG2NUM(EvtRpcLoginAuthDefault));
|
|
410
424
|
/*
|
|
411
|
-
* EVT_RPC_LOGIN_FLAGS enumeration:
|
|
425
|
+
* EVT_RPC_LOGIN_FLAGS enumeration: EvtRpcLoginAuthNegotiate
|
|
412
426
|
* @see https://docs.microsoft.com/en-us/windows/win32/api/winevt/ne-winevt-evt_rpc_login_flags
|
|
413
427
|
*/
|
|
414
428
|
rb_define_const(rb_cRpcLoginFlag, "AuthNegociate", LONG2NUM(EvtRpcLoginAuthNegotiate));
|
|
@@ -56,7 +56,7 @@ close_handles(struct WinevtSubscribe* winevtSubscribe)
|
|
|
56
56
|
winevtSubscribe->bookmark = NULL;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
for (
|
|
59
|
+
for (DWORD i = 0; i < winevtSubscribe->count; i++) {
|
|
60
60
|
if (winevtSubscribe->hEvents[i]) {
|
|
61
61
|
EvtClose(winevtSubscribe->hEvents[i]);
|
|
62
62
|
winevtSubscribe->hEvents[i] = NULL;
|
|
@@ -90,7 +90,7 @@ rb_winevt_subscribe_alloc(VALUE klass)
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
/*
|
|
93
|
-
*
|
|
93
|
+
* Initialize Subscribe class.
|
|
94
94
|
*
|
|
95
95
|
* @return [Subscribe]
|
|
96
96
|
*
|
|
@@ -111,6 +111,7 @@ rb_winevt_subscribe_initialize(VALUE self)
|
|
|
111
111
|
winevtSubscribe->preserveQualifiers = FALSE;
|
|
112
112
|
winevtSubscribe->localeInfo = &default_locale;
|
|
113
113
|
winevtSubscribe->preserveSID = TRUE;
|
|
114
|
+
winevtSubscribe->unresolvedMessageCount = 0;
|
|
114
115
|
|
|
115
116
|
return Qnil;
|
|
116
117
|
}
|
|
@@ -302,7 +303,7 @@ is_rate_limit_exceeded(struct WinevtSubscribe* winevtSubscribe)
|
|
|
302
303
|
{
|
|
303
304
|
time_t now;
|
|
304
305
|
|
|
305
|
-
if (winevtSubscribe->rateLimit == SUBSCRIBE_RATE_INFINITE)
|
|
306
|
+
if (winevtSubscribe->rateLimit == (DWORD)SUBSCRIBE_RATE_INFINITE)
|
|
306
307
|
return FALSE;
|
|
307
308
|
|
|
308
309
|
time(&now);
|
|
@@ -323,7 +324,7 @@ update_to_reflect_rate_limit_state(struct WinevtSubscribe* winevtSubscribe, ULON
|
|
|
323
324
|
{
|
|
324
325
|
time_t lastTime = 0;
|
|
325
326
|
|
|
326
|
-
if (winevtSubscribe->rateLimit == SUBSCRIBE_RATE_INFINITE)
|
|
327
|
+
if (winevtSubscribe->rateLimit == (DWORD)SUBSCRIBE_RATE_INFINITE)
|
|
327
328
|
return;
|
|
328
329
|
|
|
329
330
|
time(&lastTime);
|
|
@@ -397,8 +398,17 @@ rb_winevt_subscribe_next(VALUE self)
|
|
|
397
398
|
}
|
|
398
399
|
|
|
399
400
|
if (status == ERROR_SUCCESS) {
|
|
401
|
+
// #each frees each batch in its ensure block; this covers calling #next
|
|
402
|
+
// directly in a loop.
|
|
403
|
+
for (DWORD i = 0; i < winevtSubscribe->count; i++) {
|
|
404
|
+
if (winevtSubscribe->hEvents[i]) {
|
|
405
|
+
EvtClose(winevtSubscribe->hEvents[i]);
|
|
406
|
+
winevtSubscribe->hEvents[i] = NULL;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
400
410
|
winevtSubscribe->count = count;
|
|
401
|
-
for (
|
|
411
|
+
for (ULONG i = 0; i < count; i++) {
|
|
402
412
|
winevtSubscribe->hEvents[i] = hEvents[i];
|
|
403
413
|
EvtUpdateBookmark(winevtSubscribe->bookmark, winevtSubscribe->hEvents[i]);
|
|
404
414
|
}
|
|
@@ -428,12 +438,19 @@ rb_winevt_subscribe_render(VALUE self, EVT_HANDLE event)
|
|
|
428
438
|
}
|
|
429
439
|
|
|
430
440
|
static VALUE
|
|
431
|
-
rb_winevt_subscribe_message(
|
|
441
|
+
rb_winevt_subscribe_message(struct WinevtSubscribe* winevtSubscribe, EVT_HANDLE event)
|
|
432
442
|
{
|
|
433
443
|
WCHAR* wResult;
|
|
434
444
|
VALUE utf8str;
|
|
435
|
-
|
|
436
|
-
|
|
445
|
+
BOOL resolved = TRUE;
|
|
446
|
+
|
|
447
|
+
wResult = get_description(event,
|
|
448
|
+
winevtSubscribe->localeInfo->langID,
|
|
449
|
+
winevtSubscribe->remoteHandle,
|
|
450
|
+
&resolved);
|
|
451
|
+
if (!resolved) {
|
|
452
|
+
winevtSubscribe->unresolvedMessageCount++;
|
|
453
|
+
}
|
|
437
454
|
utf8str = wstr_to_rb_str(CP_UTF8, wResult, -1);
|
|
438
455
|
free(wResult);
|
|
439
456
|
|
|
@@ -454,7 +471,7 @@ rb_winevt_subscribe_close_handle(VALUE self)
|
|
|
454
471
|
TypedData_Get_Struct(
|
|
455
472
|
self, struct WinevtSubscribe, &rb_winevt_subscribe_type, winevtSubscribe);
|
|
456
473
|
|
|
457
|
-
for (
|
|
474
|
+
for (DWORD i = 0; i < winevtSubscribe->count; i++) {
|
|
458
475
|
if (winevtSubscribe->hEvents[i] != NULL) {
|
|
459
476
|
EvtClose(winevtSubscribe->hEvents[i]);
|
|
460
477
|
winevtSubscribe->hEvents[i] = NULL;
|
|
@@ -473,11 +490,10 @@ rb_winevt_subscribe_each_yield(VALUE self)
|
|
|
473
490
|
TypedData_Get_Struct(
|
|
474
491
|
self, struct WinevtSubscribe, &rb_winevt_subscribe_type, winevtSubscribe);
|
|
475
492
|
|
|
476
|
-
for (
|
|
493
|
+
for (DWORD i = 0; i < winevtSubscribe->count; i++) {
|
|
477
494
|
rb_yield_values(3,
|
|
478
495
|
rb_winevt_subscribe_render(self, winevtSubscribe->hEvents[i]),
|
|
479
|
-
rb_winevt_subscribe_message(winevtSubscribe->hEvents[i],
|
|
480
|
-
winevtSubscribe->remoteHandle),
|
|
496
|
+
rb_winevt_subscribe_message(winevtSubscribe, winevtSubscribe->hEvents[i]),
|
|
481
497
|
rb_winevt_subscribe_string_inserts(winevtSubscribe->hEvents[i]));
|
|
482
498
|
}
|
|
483
499
|
|
|
@@ -557,7 +573,7 @@ rb_winevt_subscribe_set_rate_limit(VALUE self, VALUE rb_rate_limit)
|
|
|
557
573
|
|
|
558
574
|
rateLimit = NUM2LONG(rb_rate_limit);
|
|
559
575
|
|
|
560
|
-
if ((rateLimit != SUBSCRIBE_RATE_INFINITE) && (rateLimit < 10 || rateLimit % 10)) {
|
|
576
|
+
if ((rateLimit != (DWORD)SUBSCRIBE_RATE_INFINITE) && (rateLimit < 10 || rateLimit % 10)) {
|
|
561
577
|
rb_raise(rb_eArgError, "Specify a multiples of 10 or RATE_INFINITE constant");
|
|
562
578
|
} else {
|
|
563
579
|
winevtSubscribe->rateLimit = rateLimit;
|
|
@@ -714,6 +730,27 @@ rb_winevt_subscribe_preserve_sid_p(VALUE self)
|
|
|
714
730
|
return winevtSubscribe->preserveSID ? Qtrue : Qfalse;
|
|
715
731
|
}
|
|
716
732
|
|
|
733
|
+
/*
|
|
734
|
+
* This method returns how many events had no description message. Message
|
|
735
|
+
* resolution fails for reasons outside the caller's control (a provider whose
|
|
736
|
+
* message DLL is gone, a locale without resources), and #each degrades to an
|
|
737
|
+
* empty message instead of aborting the enumeration. The counter increases
|
|
738
|
+
* monotonically, so callers can poll it and take the difference to tell a few
|
|
739
|
+
* unresolvable events apart from a channel that resolves nothing.
|
|
740
|
+
*
|
|
741
|
+
* @return [Integer]
|
|
742
|
+
*/
|
|
743
|
+
static VALUE
|
|
744
|
+
rb_winevt_subscribe_get_unresolved_message_count(VALUE self)
|
|
745
|
+
{
|
|
746
|
+
struct WinevtSubscribe* winevtSubscribe;
|
|
747
|
+
|
|
748
|
+
TypedData_Get_Struct(
|
|
749
|
+
self, struct WinevtSubscribe, &rb_winevt_subscribe_type, winevtSubscribe);
|
|
750
|
+
|
|
751
|
+
return ULL2NUM(winevtSubscribe->unresolvedMessageCount);
|
|
752
|
+
}
|
|
753
|
+
|
|
717
754
|
/*
|
|
718
755
|
* This method cancels channel subscription.
|
|
719
756
|
*
|
|
@@ -819,6 +856,7 @@ Init_winevt_subscribe(VALUE rb_cEventLog)
|
|
|
819
856
|
* @since 0.11.0
|
|
820
857
|
*/
|
|
821
858
|
rb_define_method(rb_cSubscribe, "preserve_sid=", rb_winevt_subscribe_set_preserve_sid, 1);
|
|
859
|
+
rb_define_method(rb_cSubscribe, "unresolved_message_count", rb_winevt_subscribe_get_unresolved_message_count, 0);
|
|
822
860
|
/*
|
|
823
861
|
* @since 0.9.1
|
|
824
862
|
*/
|
data/ext/winevt/winevt_utils.cpp
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
#include <sddl.h>
|
|
4
4
|
#include <stdlib.h>
|
|
5
|
-
#include <string>
|
|
6
5
|
#include <vector>
|
|
7
6
|
|
|
8
7
|
VALUE
|
|
@@ -127,18 +126,6 @@ connect_to_remote(LPWSTR computerName, LPWSTR domain, LPWSTR username, LPWSTR pa
|
|
|
127
126
|
return hRemote;
|
|
128
127
|
}
|
|
129
128
|
|
|
130
|
-
static std::wstring
|
|
131
|
-
guid_to_wstr(const GUID& guid)
|
|
132
|
-
{
|
|
133
|
-
LPOLESTR p = nullptr;
|
|
134
|
-
if (FAILED(StringFromCLSID(guid, &p))) {
|
|
135
|
-
return std::wstring();
|
|
136
|
-
}
|
|
137
|
-
std::wstring s(p);
|
|
138
|
-
CoTaskMemFree(p);
|
|
139
|
-
return s;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
129
|
static VALUE
|
|
143
130
|
make_displayable_binary_string(PBYTE bin, size_t length)
|
|
144
131
|
{
|
|
@@ -184,8 +171,7 @@ extract_user_evt_variants(PEVT_VARIANT pRenderedValues, DWORD propCount)
|
|
|
184
171
|
if (pRenderedValues[i].StringVal == nullptr) {
|
|
185
172
|
rb_ary_push(userValues, rb_utf8_str_new_cstr("(NULL)"));
|
|
186
173
|
} else {
|
|
187
|
-
|
|
188
|
-
rbObj = wstr_to_rb_str(CP_UTF8, &wStr[0], -1);
|
|
174
|
+
rbObj = wstr_to_rb_str(CP_UTF8, pRenderedValues[i].StringVal, -1);
|
|
189
175
|
rb_ary_push(userValues, rbObj);
|
|
190
176
|
}
|
|
191
177
|
break;
|
|
@@ -250,10 +236,9 @@ extract_user_evt_variants(PEVT_VARIANT pRenderedValues, DWORD propCount)
|
|
|
250
236
|
break;
|
|
251
237
|
case EvtVarTypeGuid:
|
|
252
238
|
if (pRenderedValues[i].GuidVal != nullptr) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
rbObj = wstr_to_rb_str(CP_UTF8, wstr.c_str(), -1);
|
|
239
|
+
WCHAR wsGuid[50];
|
|
240
|
+
if (StringFromGUID2(*pRenderedValues[i].GuidVal, wsGuid, _countof(wsGuid)) > 0) {
|
|
241
|
+
rbObj = wstr_to_rb_str(CP_UTF8, wsGuid, -1);
|
|
257
242
|
} else {
|
|
258
243
|
rbObj = rb_utf8_str_new_cstr("?");
|
|
259
244
|
}
|
|
@@ -408,130 +393,74 @@ get_values(EVT_HANDLE handle)
|
|
|
408
393
|
return userValues;
|
|
409
394
|
}
|
|
410
395
|
|
|
396
|
+
/*
|
|
397
|
+
* These statuses still leave a usable (if partial) message in the buffer. The
|
|
398
|
+
* Windows SDK sample (GetEventRawDescription.cpp) and Zabbix
|
|
399
|
+
* (src/zabbix_agent/eventlog.c) treat exactly this set the same way.
|
|
400
|
+
*/
|
|
401
|
+
static bool
|
|
402
|
+
is_partial_message(ULONG status)
|
|
403
|
+
{
|
|
404
|
+
return status == ERROR_SUCCESS ||
|
|
405
|
+
status == ERROR_EVT_UNRESOLVED_VALUE_INSERT ||
|
|
406
|
+
status == ERROR_EVT_UNRESOLVED_PARAMETER_INSERT ||
|
|
407
|
+
status == ERROR_EVT_MAX_INSERTS_REACHED;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/*
|
|
411
|
+
* Returns the formatted message, or an empty vector when it cannot be resolved.
|
|
412
|
+
* The buffer is sized to BUFSIZE / the size EvtFormatMessage asked for, not to
|
|
413
|
+
* the string, so it carries trailing L'\0' padding: read it as a NUL-terminated
|
|
414
|
+
* string, never by size(), or the padding comes along.
|
|
415
|
+
*/
|
|
411
416
|
static std::vector<WCHAR>
|
|
412
417
|
get_message(EVT_HANDLE hMetadata, EVT_HANDLE handle)
|
|
413
418
|
{
|
|
414
419
|
#define BUFSIZE 4096
|
|
415
420
|
std::vector<WCHAR> result;
|
|
416
|
-
ULONG status;
|
|
417
|
-
ULONG bufferSizeNeeded = 0;
|
|
418
|
-
LPVOID lpMsgBuf;
|
|
419
421
|
std::vector<WCHAR> message(BUFSIZE);
|
|
422
|
+
ULONG bufferSizeNeeded = 0;
|
|
423
|
+
ULONG status;
|
|
420
424
|
|
|
421
|
-
if (
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if (status != ERROR_EVT_UNRESOLVED_VALUE_INSERT) {
|
|
433
|
-
switch (status) {
|
|
434
|
-
case ERROR_EVT_MESSAGE_NOT_FOUND:
|
|
435
|
-
case ERROR_EVT_MESSAGE_ID_NOT_FOUND:
|
|
436
|
-
case ERROR_EVT_MESSAGE_LOCALE_NOT_FOUND:
|
|
437
|
-
case ERROR_RESOURCE_DATA_NOT_FOUND:
|
|
438
|
-
case ERROR_RESOURCE_TYPE_NOT_FOUND:
|
|
439
|
-
case ERROR_RESOURCE_NAME_NOT_FOUND:
|
|
440
|
-
case ERROR_RESOURCE_LANG_NOT_FOUND:
|
|
441
|
-
case ERROR_MUI_FILE_NOT_FOUND:
|
|
442
|
-
case ERROR_EVT_UNRESOLVED_PARAMETER_INSERT: {
|
|
443
|
-
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
|
444
|
-
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
445
|
-
nullptr,
|
|
446
|
-
status,
|
|
447
|
-
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
448
|
-
reinterpret_cast<WCHAR*>(&lpMsgBuf),
|
|
449
|
-
0,
|
|
450
|
-
nullptr) == 0)
|
|
451
|
-
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
|
452
|
-
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
453
|
-
nullptr,
|
|
454
|
-
status,
|
|
455
|
-
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
|
|
456
|
-
reinterpret_cast<WCHAR*>(&lpMsgBuf),
|
|
457
|
-
0,
|
|
458
|
-
nullptr);
|
|
459
|
-
|
|
460
|
-
std::wstring ret(reinterpret_cast<WCHAR*>(lpMsgBuf));
|
|
461
|
-
std::copy(ret.begin(), ret.end(), std::back_inserter(result));
|
|
462
|
-
result.push_back(L'\0');
|
|
463
|
-
LocalFree(lpMsgBuf);
|
|
464
|
-
|
|
465
|
-
goto cleanup;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
425
|
+
if (EvtFormatMessage(hMetadata,
|
|
426
|
+
handle,
|
|
427
|
+
0xffffffff,
|
|
428
|
+
0,
|
|
429
|
+
nullptr,
|
|
430
|
+
EvtFormatMessageEvent,
|
|
431
|
+
message.size(),
|
|
432
|
+
&message[0],
|
|
433
|
+
&bufferSizeNeeded)) {
|
|
434
|
+
return message;
|
|
435
|
+
}
|
|
468
436
|
|
|
469
|
-
|
|
470
|
-
|
|
437
|
+
status = GetLastError();
|
|
438
|
+
|
|
439
|
+
if (status == ERROR_INSUFFICIENT_BUFFER) {
|
|
440
|
+
message.resize(bufferSizeNeeded);
|
|
441
|
+
|
|
442
|
+
if (EvtFormatMessage(hMetadata,
|
|
443
|
+
handle,
|
|
444
|
+
0xffffffff,
|
|
445
|
+
0,
|
|
446
|
+
nullptr,
|
|
447
|
+
EvtFormatMessageEvent,
|
|
448
|
+
message.size(),
|
|
449
|
+
&message.front(),
|
|
450
|
+
&bufferSizeNeeded)) {
|
|
451
|
+
return message;
|
|
471
452
|
}
|
|
472
453
|
|
|
473
|
-
|
|
474
|
-
message.resize(bufferSizeNeeded);
|
|
475
|
-
message.shrink_to_fit();
|
|
476
|
-
|
|
477
|
-
if (!EvtFormatMessage(hMetadata,
|
|
478
|
-
handle,
|
|
479
|
-
0xffffffff,
|
|
480
|
-
0,
|
|
481
|
-
nullptr,
|
|
482
|
-
EvtFormatMessageEvent,
|
|
483
|
-
message.size(),
|
|
484
|
-
&message.front(),
|
|
485
|
-
&bufferSizeNeeded)) {
|
|
486
|
-
status = GetLastError();
|
|
487
|
-
|
|
488
|
-
if (status != ERROR_EVT_UNRESOLVED_VALUE_INSERT) {
|
|
489
|
-
switch (status) {
|
|
490
|
-
case ERROR_EVT_MESSAGE_NOT_FOUND:
|
|
491
|
-
case ERROR_EVT_MESSAGE_ID_NOT_FOUND:
|
|
492
|
-
case ERROR_EVT_MESSAGE_LOCALE_NOT_FOUND:
|
|
493
|
-
case ERROR_RESOURCE_DATA_NOT_FOUND:
|
|
494
|
-
case ERROR_RESOURCE_TYPE_NOT_FOUND:
|
|
495
|
-
case ERROR_RESOURCE_NAME_NOT_FOUND:
|
|
496
|
-
case ERROR_RESOURCE_LANG_NOT_FOUND:
|
|
497
|
-
case ERROR_MUI_FILE_NOT_FOUND:
|
|
498
|
-
case ERROR_EVT_UNRESOLVED_PARAMETER_INSERT:
|
|
499
|
-
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
500
|
-
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
501
|
-
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
502
|
-
nullptr,
|
|
503
|
-
status,
|
|
504
|
-
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
505
|
-
reinterpret_cast<WCHAR*>(&lpMsgBuf),
|
|
506
|
-
0,
|
|
507
|
-
nullptr) == 0)
|
|
508
|
-
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
509
|
-
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
510
|
-
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
511
|
-
nullptr,
|
|
512
|
-
status,
|
|
513
|
-
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
|
|
514
|
-
reinterpret_cast<WCHAR*>(&lpMsgBuf),
|
|
515
|
-
0,
|
|
516
|
-
nullptr);
|
|
517
|
-
|
|
518
|
-
std::wstring ret(reinterpret_cast<WCHAR*>(lpMsgBuf));
|
|
519
|
-
std::copy(ret.begin(), ret.end(), std::back_inserter(result));
|
|
520
|
-
result.push_back(L'\0');
|
|
521
|
-
LocalFree(lpMsgBuf);
|
|
522
|
-
|
|
523
|
-
goto cleanup;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
rb_raise(rb_eWinevtQueryError, "ErrorCode: %lu", status);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
454
|
+
status = GetLastError();
|
|
530
455
|
}
|
|
531
456
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
457
|
+
// Message resolution fails for open-ended reasons -- a message DLL removed
|
|
458
|
+
// from disk, a locale without resources, anything LoadLibraryEx surfaces --
|
|
459
|
+
// so there is no error set to enumerate. Raising on "unknown" statuses would
|
|
460
|
+
// let one unresolvable event abort the whole #each enumeration.
|
|
461
|
+
if (is_partial_message(status)) {
|
|
462
|
+
result = message;
|
|
463
|
+
}
|
|
535
464
|
|
|
536
465
|
return result;
|
|
537
466
|
|
|
@@ -539,27 +468,37 @@ cleanup:
|
|
|
539
468
|
}
|
|
540
469
|
|
|
541
470
|
WCHAR*
|
|
542
|
-
get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote)
|
|
471
|
+
get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote, BOOL* resolved)
|
|
543
472
|
{
|
|
544
473
|
#define BUFSIZE 4096
|
|
545
|
-
|
|
474
|
+
// rb_raise longjmps out of this frame without running C++ destructors, and
|
|
475
|
+
// both error paths below raise. Ruby owns this buffer so the GC still
|
|
476
|
+
// reclaims it on those paths; a std::vector here would leak its storage.
|
|
477
|
+
VALUE vbuffer;
|
|
478
|
+
WCHAR* buffer = RB_ALLOCV_N(WCHAR, vbuffer, BUFSIZE);
|
|
546
479
|
ULONG bufferSizeNeeded = 0;
|
|
547
480
|
ULONG status, count;
|
|
548
481
|
std::vector<WCHAR> result;
|
|
549
482
|
EVT_HANDLE hMetadata = nullptr;
|
|
550
483
|
|
|
484
|
+
if (resolved)
|
|
485
|
+
*resolved = TRUE;
|
|
486
|
+
|
|
551
487
|
static PCWSTR eventProperties[] = { L"Event/System/Provider/@Name" };
|
|
552
488
|
EVT_HANDLE renderContext =
|
|
553
489
|
EvtCreateRenderContext(1, eventProperties, EvtRenderContextValues);
|
|
554
490
|
if (renderContext == nullptr) {
|
|
491
|
+
RB_ALLOCV_END(vbuffer);
|
|
555
492
|
rb_raise(rb_eWinevtQueryError, "Failed to create renderContext");
|
|
556
493
|
}
|
|
557
494
|
|
|
495
|
+
// EvtRender sizes in bytes, not characters, and this call has no retry: a
|
|
496
|
+
// short size turns into ERROR_INSUFFICIENT_BUFFER and raises below.
|
|
558
497
|
if (EvtRender(renderContext,
|
|
559
498
|
handle,
|
|
560
499
|
EvtRenderEventValues,
|
|
561
|
-
|
|
562
|
-
|
|
500
|
+
BUFSIZE * sizeof(WCHAR),
|
|
501
|
+
buffer,
|
|
563
502
|
&bufferSizeNeeded,
|
|
564
503
|
&count) != FALSE) {
|
|
565
504
|
status = ERROR_SUCCESS;
|
|
@@ -569,11 +508,20 @@ get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote)
|
|
|
569
508
|
|
|
570
509
|
if (status != ERROR_SUCCESS) {
|
|
571
510
|
EvtClose(renderContext);
|
|
511
|
+
RB_ALLOCV_END(vbuffer);
|
|
572
512
|
raise_system_error(rb_eWinevtQueryError, status);
|
|
573
513
|
}
|
|
574
514
|
|
|
575
|
-
// Obtain buffer as EVT_VARIANT pointer. To avoid
|
|
576
|
-
const PEVT_VARIANT values = reinterpret_cast<PEVT_VARIANT>(
|
|
515
|
+
// Obtain buffer as EVT_VARIANT pointer. To avoid ErrorCode 87 in EvtRender.
|
|
516
|
+
const PEVT_VARIANT values = reinterpret_cast<PEVT_VARIANT>(buffer);
|
|
517
|
+
|
|
518
|
+
// EvtRender reports what it actually wrote, and the buffer is not zeroed, so
|
|
519
|
+
// values[0] is only meaningful once count says it was filled in. A truncated
|
|
520
|
+
// event -- more likely arriving over a remote session -- would otherwise hand
|
|
521
|
+
// EvtOpenPublisherMetadata whatever the buffer happened to contain.
|
|
522
|
+
if (count == 0 || values[0].Type != EvtVarTypeString) {
|
|
523
|
+
goto cleanup;
|
|
524
|
+
}
|
|
577
525
|
|
|
578
526
|
// Open publisher metadata
|
|
579
527
|
hMetadata = EvtOpenPublisherMetadata(
|
|
@@ -594,13 +542,19 @@ get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote)
|
|
|
594
542
|
|
|
595
543
|
cleanup:
|
|
596
544
|
|
|
545
|
+
RB_ALLOCV_END(vbuffer);
|
|
546
|
+
|
|
597
547
|
if (renderContext)
|
|
598
548
|
EvtClose(renderContext);
|
|
599
549
|
|
|
600
550
|
if (hMetadata)
|
|
601
551
|
EvtClose(hMetadata);
|
|
602
552
|
|
|
553
|
+
// Both ways of giving up -- publisher metadata that cannot be opened, and
|
|
554
|
+
// EvtFormatMessage failing to resolve -- end up here with an empty result.
|
|
603
555
|
if (result.empty()) {
|
|
556
|
+
if (resolved)
|
|
557
|
+
*resolved = FALSE;
|
|
604
558
|
return _wcsdup(L"");
|
|
605
559
|
}
|
|
606
560
|
|
|
@@ -713,7 +667,10 @@ render_system_event(EVT_HANDLE hEvent, BOOL preserve_qualifiers, BOOL preserveSI
|
|
|
713
667
|
DWORD dwBufferSize = 0;
|
|
714
668
|
DWORD dwBufferUsed = 0;
|
|
715
669
|
DWORD dwPropertyCount = 0;
|
|
716
|
-
|
|
670
|
+
// The error path below runs RB_ALLOCV_END() whether or not RB_ALLOCV() was
|
|
671
|
+
// reached, and RB_ALLOCV_END() dereferences anything non-zero. Zero, not
|
|
672
|
+
// Qnil, is the value it treats as "nothing was allocated".
|
|
673
|
+
VALUE vRenderedValues = 0;
|
|
717
674
|
PEVT_VARIANT pRenderedValues = NULL;
|
|
718
675
|
WCHAR wsGuid[50];
|
|
719
676
|
LPSTR pwsSid = NULL;
|
|
@@ -858,16 +815,20 @@ render_system_event(EVT_HANDLE hEvent, BOOL preserve_qualifiers, BOOL preserveSI
|
|
|
858
815
|
rb_str_new2("TimeCreated"),
|
|
859
816
|
Qnil);
|
|
860
817
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
818
|
+
if (EvtVarTypeNull != pRenderedValues[EvtSystemEventRecordId].Type) {
|
|
819
|
+
_snprintf_s(buffer,
|
|
820
|
+
_countof(buffer),
|
|
821
|
+
_TRUNCATE,
|
|
822
|
+
"%llu",
|
|
823
|
+
pRenderedValues[EvtSystemEventRecordId].UInt64Val);
|
|
824
|
+
rb_hash_aset(hash,
|
|
825
|
+
rb_str_new2("EventRecordID"),
|
|
826
|
+
rb_str_new2(buffer));
|
|
827
|
+
} else {
|
|
828
|
+
rb_hash_aset(hash,
|
|
829
|
+
rb_str_new2("EventRecordID"),
|
|
830
|
+
Qnil);
|
|
831
|
+
}
|
|
871
832
|
|
|
872
833
|
if (EvtVarTypeNull != pRenderedValues[EvtSystemActivityID].Type) {
|
|
873
834
|
const GUID* Guid = pRenderedValues[EvtSystemActivityID].GuidVal;
|
data/lib/winevt/version.rb
CHANGED
data/winevt_c.gemspec
CHANGED
|
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
|
23
23
|
spec.require_paths = ["lib"]
|
|
24
24
|
spec.extensions = ["ext/winevt/extconf.rb"]
|
|
25
25
|
spec.license = "Apache-2.0"
|
|
26
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2
|
|
26
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.2".freeze)
|
|
27
27
|
|
|
28
28
|
spec.add_development_dependency "bundler"
|
|
29
29
|
spec.add_development_dependency "rake", "~> 13.0"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: winevt_c
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.11.
|
|
4
|
+
version: 0.11.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hiroshi Hatake
|
|
@@ -132,14 +132,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
132
132
|
requirements:
|
|
133
133
|
- - ">="
|
|
134
134
|
- !ruby/object:Gem::Version
|
|
135
|
-
version: '2
|
|
135
|
+
version: '3.2'
|
|
136
136
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
137
137
|
requirements:
|
|
138
138
|
- - ">="
|
|
139
139
|
- !ruby/object:Gem::Version
|
|
140
140
|
version: '0'
|
|
141
141
|
requirements: []
|
|
142
|
-
rubygems_version: 4.0.
|
|
142
|
+
rubygems_version: 4.0.16
|
|
143
143
|
specification_version: 4
|
|
144
144
|
summary: Windows Event Log API bindings from winevt.h.
|
|
145
145
|
test_files: []
|