winevt_c 0.11.4 → 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 +10 -5
- 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 +101 -14
- data/ext/winevt/winevt_session.c +18 -4
- data/ext/winevt/winevt_subscribe.c +69 -27
- data/ext/winevt/winevt_utils.cpp +116 -145
- 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
|
@@ -11,20 +11,25 @@ jobs:
|
|
|
11
11
|
uses: ruby/actions/.github/workflows/ruby_versions.yml@master
|
|
12
12
|
with:
|
|
13
13
|
engine: cruby
|
|
14
|
-
min_version: 3.
|
|
14
|
+
min_version: 3.2
|
|
15
15
|
build:
|
|
16
16
|
needs: ruby-versions
|
|
17
|
-
runs-on:
|
|
17
|
+
runs-on: ${{ matrix.os }}
|
|
18
18
|
strategy:
|
|
19
19
|
fail-fast: false
|
|
20
20
|
matrix:
|
|
21
|
+
os: ['windows-latest', 'windows-11-arm']
|
|
21
22
|
ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
|
|
22
23
|
exclude:
|
|
23
24
|
- ruby: head
|
|
24
|
-
|
|
25
|
+
- os: 'windows-11-arm'
|
|
26
|
+
ruby: '3.3'
|
|
27
|
+
- os: 'windows-11-arm'
|
|
28
|
+
ruby: '3.2'
|
|
29
|
+
name: Ruby ${{ matrix.ruby }} on ${{ matrix.os }}
|
|
25
30
|
steps:
|
|
26
|
-
- uses: actions/checkout@
|
|
27
|
-
- uses: ruby/setup-ruby@
|
|
31
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
32
|
+
- uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
|
|
28
33
|
with:
|
|
29
34
|
ruby-version: ${{ matrix.ruby }}
|
|
30
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
+
|
|
196
|
+
if (winevtQuery->query == NULL) {
|
|
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
|
}
|
|
@@ -175,8 +176,6 @@ rb_winevt_subscribe_subscribe(int argc, VALUE* argv, VALUE self)
|
|
|
175
176
|
struct WinevtSession* winevtSession;
|
|
176
177
|
struct WinevtSubscribe* winevtSubscribe;
|
|
177
178
|
|
|
178
|
-
hSignalEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
|
|
179
|
-
|
|
180
179
|
TypedData_Get_Struct(
|
|
181
180
|
self, struct WinevtSubscribe, &rb_winevt_subscribe_type, winevtSubscribe);
|
|
182
181
|
|
|
@@ -239,6 +238,7 @@ rb_winevt_subscribe_subscribe(int argc, VALUE* argv, VALUE self)
|
|
|
239
238
|
flags |= EvtSubscribeToFutureEvents;
|
|
240
239
|
}
|
|
241
240
|
|
|
241
|
+
hSignalEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
|
|
242
242
|
hSubscription =
|
|
243
243
|
EvtSubscribe(hRemoteHandle, hSignalEvent, path, query, hBookmark, NULL, NULL, flags);
|
|
244
244
|
if (!hSubscription) {
|
|
@@ -263,22 +263,13 @@ rb_winevt_subscribe_subscribe(int argc, VALUE* argv, VALUE self)
|
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
-
if (winevtSubscribe->subscription != NULL) {
|
|
267
|
-
// should be disgarded the old event subscription handle.
|
|
268
|
-
EvtClose(winevtSubscribe->subscription);
|
|
269
|
-
}
|
|
270
266
|
|
|
271
267
|
ALLOCV_END(wpathBuf);
|
|
272
268
|
ALLOCV_END(wqueryBuf);
|
|
273
269
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (hBookmark) {
|
|
278
|
-
winevtSubscribe->bookmark = hBookmark;
|
|
279
|
-
} else {
|
|
280
|
-
winevtSubscribe->bookmark = EvtCreateBookmark(NULL);
|
|
281
|
-
if (winevtSubscribe->bookmark == NULL) {
|
|
270
|
+
if (!hBookmark) {
|
|
271
|
+
hBookmark = EvtCreateBookmark(NULL);
|
|
272
|
+
if (hBookmark == NULL) {
|
|
282
273
|
status = GetLastError();
|
|
283
274
|
if (hSubscription != NULL) {
|
|
284
275
|
EvtClose(hSubscription);
|
|
@@ -290,6 +281,20 @@ rb_winevt_subscribe_subscribe(int argc, VALUE* argv, VALUE self)
|
|
|
290
281
|
}
|
|
291
282
|
}
|
|
292
283
|
|
|
284
|
+
if (winevtSubscribe->subscription) {
|
|
285
|
+
EvtClose(winevtSubscribe->subscription);
|
|
286
|
+
}
|
|
287
|
+
if (winevtSubscribe->signalEvent) {
|
|
288
|
+
CloseHandle(winevtSubscribe->signalEvent);
|
|
289
|
+
}
|
|
290
|
+
if (winevtSubscribe->bookmark) {
|
|
291
|
+
EvtClose(winevtSubscribe->bookmark);
|
|
292
|
+
}
|
|
293
|
+
winevtSubscribe->signalEvent = hSignalEvent;
|
|
294
|
+
winevtSubscribe->subscription = hSubscription;
|
|
295
|
+
winevtSubscribe->remoteHandle = hRemoteHandle;
|
|
296
|
+
winevtSubscribe->bookmark = hBookmark;
|
|
297
|
+
|
|
293
298
|
return Qtrue;
|
|
294
299
|
}
|
|
295
300
|
|
|
@@ -298,7 +303,7 @@ is_rate_limit_exceeded(struct WinevtSubscribe* winevtSubscribe)
|
|
|
298
303
|
{
|
|
299
304
|
time_t now;
|
|
300
305
|
|
|
301
|
-
if (winevtSubscribe->rateLimit == SUBSCRIBE_RATE_INFINITE)
|
|
306
|
+
if (winevtSubscribe->rateLimit == (DWORD)SUBSCRIBE_RATE_INFINITE)
|
|
302
307
|
return FALSE;
|
|
303
308
|
|
|
304
309
|
time(&now);
|
|
@@ -319,7 +324,7 @@ update_to_reflect_rate_limit_state(struct WinevtSubscribe* winevtSubscribe, ULON
|
|
|
319
324
|
{
|
|
320
325
|
time_t lastTime = 0;
|
|
321
326
|
|
|
322
|
-
if (winevtSubscribe->rateLimit == SUBSCRIBE_RATE_INFINITE)
|
|
327
|
+
if (winevtSubscribe->rateLimit == (DWORD)SUBSCRIBE_RATE_INFINITE)
|
|
323
328
|
return;
|
|
324
329
|
|
|
325
330
|
time(&lastTime);
|
|
@@ -393,8 +398,17 @@ rb_winevt_subscribe_next(VALUE self)
|
|
|
393
398
|
}
|
|
394
399
|
|
|
395
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
|
+
|
|
396
410
|
winevtSubscribe->count = count;
|
|
397
|
-
for (
|
|
411
|
+
for (ULONG i = 0; i < count; i++) {
|
|
398
412
|
winevtSubscribe->hEvents[i] = hEvents[i];
|
|
399
413
|
EvtUpdateBookmark(winevtSubscribe->bookmark, winevtSubscribe->hEvents[i]);
|
|
400
414
|
}
|
|
@@ -424,12 +438,19 @@ rb_winevt_subscribe_render(VALUE self, EVT_HANDLE event)
|
|
|
424
438
|
}
|
|
425
439
|
|
|
426
440
|
static VALUE
|
|
427
|
-
rb_winevt_subscribe_message(
|
|
441
|
+
rb_winevt_subscribe_message(struct WinevtSubscribe* winevtSubscribe, EVT_HANDLE event)
|
|
428
442
|
{
|
|
429
443
|
WCHAR* wResult;
|
|
430
444
|
VALUE utf8str;
|
|
431
|
-
|
|
432
|
-
|
|
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
|
+
}
|
|
433
454
|
utf8str = wstr_to_rb_str(CP_UTF8, wResult, -1);
|
|
434
455
|
free(wResult);
|
|
435
456
|
|
|
@@ -450,7 +471,7 @@ rb_winevt_subscribe_close_handle(VALUE self)
|
|
|
450
471
|
TypedData_Get_Struct(
|
|
451
472
|
self, struct WinevtSubscribe, &rb_winevt_subscribe_type, winevtSubscribe);
|
|
452
473
|
|
|
453
|
-
for (
|
|
474
|
+
for (DWORD i = 0; i < winevtSubscribe->count; i++) {
|
|
454
475
|
if (winevtSubscribe->hEvents[i] != NULL) {
|
|
455
476
|
EvtClose(winevtSubscribe->hEvents[i]);
|
|
456
477
|
winevtSubscribe->hEvents[i] = NULL;
|
|
@@ -469,11 +490,10 @@ rb_winevt_subscribe_each_yield(VALUE self)
|
|
|
469
490
|
TypedData_Get_Struct(
|
|
470
491
|
self, struct WinevtSubscribe, &rb_winevt_subscribe_type, winevtSubscribe);
|
|
471
492
|
|
|
472
|
-
for (
|
|
493
|
+
for (DWORD i = 0; i < winevtSubscribe->count; i++) {
|
|
473
494
|
rb_yield_values(3,
|
|
474
495
|
rb_winevt_subscribe_render(self, winevtSubscribe->hEvents[i]),
|
|
475
|
-
rb_winevt_subscribe_message(winevtSubscribe->hEvents[i],
|
|
476
|
-
winevtSubscribe->remoteHandle),
|
|
496
|
+
rb_winevt_subscribe_message(winevtSubscribe, winevtSubscribe->hEvents[i]),
|
|
477
497
|
rb_winevt_subscribe_string_inserts(winevtSubscribe->hEvents[i]));
|
|
478
498
|
}
|
|
479
499
|
|
|
@@ -553,7 +573,7 @@ rb_winevt_subscribe_set_rate_limit(VALUE self, VALUE rb_rate_limit)
|
|
|
553
573
|
|
|
554
574
|
rateLimit = NUM2LONG(rb_rate_limit);
|
|
555
575
|
|
|
556
|
-
if ((rateLimit != SUBSCRIBE_RATE_INFINITE) && (rateLimit < 10 || rateLimit % 10)) {
|
|
576
|
+
if ((rateLimit != (DWORD)SUBSCRIBE_RATE_INFINITE) && (rateLimit < 10 || rateLimit % 10)) {
|
|
557
577
|
rb_raise(rb_eArgError, "Specify a multiples of 10 or RATE_INFINITE constant");
|
|
558
578
|
} else {
|
|
559
579
|
winevtSubscribe->rateLimit = rateLimit;
|
|
@@ -710,6 +730,27 @@ rb_winevt_subscribe_preserve_sid_p(VALUE self)
|
|
|
710
730
|
return winevtSubscribe->preserveSID ? Qtrue : Qfalse;
|
|
711
731
|
}
|
|
712
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
|
+
|
|
713
754
|
/*
|
|
714
755
|
* This method cancels channel subscription.
|
|
715
756
|
*
|
|
@@ -815,6 +856,7 @@ Init_winevt_subscribe(VALUE rb_cEventLog)
|
|
|
815
856
|
* @since 0.11.0
|
|
816
857
|
*/
|
|
817
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);
|
|
818
860
|
/*
|
|
819
861
|
* @since 0.9.1
|
|
820
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 nullptr;
|
|
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,9 +236,12 @@ 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
|
-
|
|
239
|
+
WCHAR wsGuid[50];
|
|
240
|
+
if (StringFromGUID2(*pRenderedValues[i].GuidVal, wsGuid, _countof(wsGuid)) > 0) {
|
|
241
|
+
rbObj = wstr_to_rb_str(CP_UTF8, wsGuid, -1);
|
|
242
|
+
} else {
|
|
243
|
+
rbObj = rb_utf8_str_new_cstr("?");
|
|
244
|
+
}
|
|
256
245
|
rb_ary_push(userValues, rbObj);
|
|
257
246
|
} else {
|
|
258
247
|
rb_ary_push(userValues, rb_utf8_str_new_cstr("?"));
|
|
@@ -404,128 +393,74 @@ get_values(EVT_HANDLE handle)
|
|
|
404
393
|
return userValues;
|
|
405
394
|
}
|
|
406
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
|
+
*/
|
|
407
416
|
static std::vector<WCHAR>
|
|
408
417
|
get_message(EVT_HANDLE hMetadata, EVT_HANDLE handle)
|
|
409
418
|
{
|
|
410
419
|
#define BUFSIZE 4096
|
|
411
420
|
std::vector<WCHAR> result;
|
|
412
|
-
ULONG status;
|
|
413
|
-
ULONG bufferSizeNeeded = 0;
|
|
414
|
-
LPVOID lpMsgBuf;
|
|
415
421
|
std::vector<WCHAR> message(BUFSIZE);
|
|
422
|
+
ULONG bufferSizeNeeded = 0;
|
|
423
|
+
ULONG status;
|
|
416
424
|
|
|
417
|
-
if (
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
if (status != ERROR_EVT_UNRESOLVED_VALUE_INSERT) {
|
|
429
|
-
switch (status) {
|
|
430
|
-
case ERROR_EVT_MESSAGE_NOT_FOUND:
|
|
431
|
-
case ERROR_EVT_MESSAGE_ID_NOT_FOUND:
|
|
432
|
-
case ERROR_EVT_MESSAGE_LOCALE_NOT_FOUND:
|
|
433
|
-
case ERROR_RESOURCE_DATA_NOT_FOUND:
|
|
434
|
-
case ERROR_RESOURCE_TYPE_NOT_FOUND:
|
|
435
|
-
case ERROR_RESOURCE_NAME_NOT_FOUND:
|
|
436
|
-
case ERROR_RESOURCE_LANG_NOT_FOUND:
|
|
437
|
-
case ERROR_MUI_FILE_NOT_FOUND:
|
|
438
|
-
case ERROR_EVT_UNRESOLVED_PARAMETER_INSERT: {
|
|
439
|
-
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
|
440
|
-
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
441
|
-
nullptr,
|
|
442
|
-
status,
|
|
443
|
-
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
444
|
-
reinterpret_cast<WCHAR*>(&lpMsgBuf),
|
|
445
|
-
0,
|
|
446
|
-
nullptr) == 0)
|
|
447
|
-
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
|
448
|
-
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
449
|
-
nullptr,
|
|
450
|
-
status,
|
|
451
|
-
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
|
|
452
|
-
reinterpret_cast<WCHAR*>(&lpMsgBuf),
|
|
453
|
-
0,
|
|
454
|
-
nullptr);
|
|
455
|
-
|
|
456
|
-
std::wstring ret(reinterpret_cast<WCHAR*>(lpMsgBuf));
|
|
457
|
-
std::copy(ret.begin(), ret.end(), std::back_inserter(result));
|
|
458
|
-
LocalFree(lpMsgBuf);
|
|
459
|
-
|
|
460
|
-
goto cleanup;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
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
|
+
}
|
|
463
436
|
|
|
464
|
-
|
|
465
|
-
|
|
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;
|
|
466
452
|
}
|
|
467
453
|
|
|
468
|
-
|
|
469
|
-
message.resize(bufferSizeNeeded);
|
|
470
|
-
message.shrink_to_fit();
|
|
471
|
-
|
|
472
|
-
if (!EvtFormatMessage(hMetadata,
|
|
473
|
-
handle,
|
|
474
|
-
0xffffffff,
|
|
475
|
-
0,
|
|
476
|
-
nullptr,
|
|
477
|
-
EvtFormatMessageEvent,
|
|
478
|
-
message.size(),
|
|
479
|
-
&message.front(),
|
|
480
|
-
&bufferSizeNeeded)) {
|
|
481
|
-
status = GetLastError();
|
|
482
|
-
|
|
483
|
-
if (status != ERROR_EVT_UNRESOLVED_VALUE_INSERT) {
|
|
484
|
-
switch (status) {
|
|
485
|
-
case ERROR_EVT_MESSAGE_NOT_FOUND:
|
|
486
|
-
case ERROR_EVT_MESSAGE_ID_NOT_FOUND:
|
|
487
|
-
case ERROR_EVT_MESSAGE_LOCALE_NOT_FOUND:
|
|
488
|
-
case ERROR_RESOURCE_DATA_NOT_FOUND:
|
|
489
|
-
case ERROR_RESOURCE_TYPE_NOT_FOUND:
|
|
490
|
-
case ERROR_RESOURCE_NAME_NOT_FOUND:
|
|
491
|
-
case ERROR_RESOURCE_LANG_NOT_FOUND:
|
|
492
|
-
case ERROR_MUI_FILE_NOT_FOUND:
|
|
493
|
-
case ERROR_EVT_UNRESOLVED_PARAMETER_INSERT:
|
|
494
|
-
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
495
|
-
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
496
|
-
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
497
|
-
nullptr,
|
|
498
|
-
status,
|
|
499
|
-
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
500
|
-
reinterpret_cast<WCHAR*>(&lpMsgBuf),
|
|
501
|
-
0,
|
|
502
|
-
nullptr) == 0)
|
|
503
|
-
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
504
|
-
FORMAT_MESSAGE_FROM_SYSTEM |
|
|
505
|
-
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
506
|
-
nullptr,
|
|
507
|
-
status,
|
|
508
|
-
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
|
|
509
|
-
reinterpret_cast<WCHAR*>(&lpMsgBuf),
|
|
510
|
-
0,
|
|
511
|
-
nullptr);
|
|
512
|
-
|
|
513
|
-
std::wstring ret(reinterpret_cast<WCHAR*>(lpMsgBuf));
|
|
514
|
-
std::copy(ret.begin(), ret.end(), std::back_inserter(result));
|
|
515
|
-
LocalFree(lpMsgBuf);
|
|
516
|
-
|
|
517
|
-
goto cleanup;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
rb_raise(rb_eWinevtQueryError, "ErrorCode: %lu", status);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
454
|
+
status = GetLastError();
|
|
524
455
|
}
|
|
525
456
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
+
}
|
|
529
464
|
|
|
530
465
|
return result;
|
|
531
466
|
|
|
@@ -533,27 +468,37 @@ cleanup:
|
|
|
533
468
|
}
|
|
534
469
|
|
|
535
470
|
WCHAR*
|
|
536
|
-
get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote)
|
|
471
|
+
get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote, BOOL* resolved)
|
|
537
472
|
{
|
|
538
473
|
#define BUFSIZE 4096
|
|
539
|
-
|
|
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);
|
|
540
479
|
ULONG bufferSizeNeeded = 0;
|
|
541
480
|
ULONG status, count;
|
|
542
481
|
std::vector<WCHAR> result;
|
|
543
482
|
EVT_HANDLE hMetadata = nullptr;
|
|
544
483
|
|
|
484
|
+
if (resolved)
|
|
485
|
+
*resolved = TRUE;
|
|
486
|
+
|
|
545
487
|
static PCWSTR eventProperties[] = { L"Event/System/Provider/@Name" };
|
|
546
488
|
EVT_HANDLE renderContext =
|
|
547
489
|
EvtCreateRenderContext(1, eventProperties, EvtRenderContextValues);
|
|
548
490
|
if (renderContext == nullptr) {
|
|
491
|
+
RB_ALLOCV_END(vbuffer);
|
|
549
492
|
rb_raise(rb_eWinevtQueryError, "Failed to create renderContext");
|
|
550
493
|
}
|
|
551
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.
|
|
552
497
|
if (EvtRender(renderContext,
|
|
553
498
|
handle,
|
|
554
499
|
EvtRenderEventValues,
|
|
555
|
-
|
|
556
|
-
|
|
500
|
+
BUFSIZE * sizeof(WCHAR),
|
|
501
|
+
buffer,
|
|
557
502
|
&bufferSizeNeeded,
|
|
558
503
|
&count) != FALSE) {
|
|
559
504
|
status = ERROR_SUCCESS;
|
|
@@ -563,11 +508,20 @@ get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote)
|
|
|
563
508
|
|
|
564
509
|
if (status != ERROR_SUCCESS) {
|
|
565
510
|
EvtClose(renderContext);
|
|
511
|
+
RB_ALLOCV_END(vbuffer);
|
|
566
512
|
raise_system_error(rb_eWinevtQueryError, status);
|
|
567
513
|
}
|
|
568
514
|
|
|
569
|
-
// Obtain buffer as EVT_VARIANT pointer. To avoid
|
|
570
|
-
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
|
+
}
|
|
571
525
|
|
|
572
526
|
// Open publisher metadata
|
|
573
527
|
hMetadata = EvtOpenPublisherMetadata(
|
|
@@ -588,12 +542,22 @@ get_description(EVT_HANDLE handle, LANGID langID, EVT_HANDLE hRemote)
|
|
|
588
542
|
|
|
589
543
|
cleanup:
|
|
590
544
|
|
|
545
|
+
RB_ALLOCV_END(vbuffer);
|
|
546
|
+
|
|
591
547
|
if (renderContext)
|
|
592
548
|
EvtClose(renderContext);
|
|
593
549
|
|
|
594
550
|
if (hMetadata)
|
|
595
551
|
EvtClose(hMetadata);
|
|
596
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.
|
|
555
|
+
if (result.empty()) {
|
|
556
|
+
if (resolved)
|
|
557
|
+
*resolved = FALSE;
|
|
558
|
+
return _wcsdup(L"");
|
|
559
|
+
}
|
|
560
|
+
|
|
597
561
|
return _wcsdup(result.data());
|
|
598
562
|
}
|
|
599
563
|
|
|
@@ -703,7 +667,10 @@ render_system_event(EVT_HANDLE hEvent, BOOL preserve_qualifiers, BOOL preserveSI
|
|
|
703
667
|
DWORD dwBufferSize = 0;
|
|
704
668
|
DWORD dwBufferUsed = 0;
|
|
705
669
|
DWORD dwPropertyCount = 0;
|
|
706
|
-
|
|
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;
|
|
707
674
|
PEVT_VARIANT pRenderedValues = NULL;
|
|
708
675
|
WCHAR wsGuid[50];
|
|
709
676
|
LPSTR pwsSid = NULL;
|
|
@@ -848,16 +815,20 @@ render_system_event(EVT_HANDLE hEvent, BOOL preserve_qualifiers, BOOL preserveSI
|
|
|
848
815
|
rb_str_new2("TimeCreated"),
|
|
849
816
|
Qnil);
|
|
850
817
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
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
|
+
}
|
|
861
832
|
|
|
862
833
|
if (EvtVarTypeNull != pRenderedValues[EvtSystemActivityID].Type) {
|
|
863
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: []
|