fluent-plugin-windows-eventlog 0.8.0 → 0.8.2

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.
@@ -1,402 +1,410 @@
1
- require 'winevt'
2
- require 'fluent/plugin/input'
3
- require 'fluent/plugin'
4
- require_relative 'bookmark_sax_parser'
5
-
6
- module Fluent::Plugin
7
- class WindowsEventLog2Input < Input
8
- Fluent::Plugin.register_input('windows_eventlog2', self)
9
-
10
- class ReconnectError < Fluent::UnrecoverableError; end
11
-
12
- helpers :timer, :storage, :parser
13
-
14
- DEFAULT_STORAGE_TYPE = 'local'
15
- KEY_MAP = {"ProviderName" => ["ProviderName", :string],
16
- "ProviderGUID" => ["ProviderGUID", :string],
17
- "EventID" => ["EventID", :string],
18
- "Qualifiers" => ["Qualifiers", :string],
19
- "Level" => ["Level", :string],
20
- "Task" => ["Task", :string],
21
- "Opcode" => ["Opcode", :string],
22
- "Keywords" => ["Keywords", :string],
23
- "TimeCreated" => ["TimeCreated", :string],
24
- "EventRecordID" => ["EventRecordID", :string],
25
- "ActivityID" => ["ActivityID", :string],
26
- "RelatedActivityID" => ["RelatedActivityID", :string],
27
- "ProcessID" => ["ProcessID", :string],
28
- "ThreadID" => ["ThreadID", :string],
29
- "Channel" => ["Channel", :string],
30
- "Computer" => ["Computer", :string],
31
- "UserID" => ["UserID", :string],
32
- "Version" => ["Version", :string],
33
- "Description" => ["Description", :string],
34
- "EventData" => ["EventData", :array]}
35
-
36
- config_param :tag, :string
37
- config_param :read_interval, :time, default: 2
38
- config_param :channels, :array, default: []
39
- config_param :keys, :array, default: []
40
- config_param :read_from_head, :bool, default: false, deprecated: "Use `read_existing_events' instead."
41
- config_param :read_existing_events, :bool, default: false
42
- config_param :parse_description, :bool, default: false
43
- config_param :render_as_xml, :bool, default: false
44
- config_param :rate_limit, :integer, default: Winevt::EventLog::Subscribe::RATE_INFINITE
45
- config_param :preserve_qualifiers_on_hash, :bool, default: false
46
- config_param :read_all_channels, :bool, default: false
47
- config_param :description_locale, :string, default: nil
48
- config_param :refresh_subscription_interval, :time, default: nil
49
-
50
- config_section :subscribe, param_name: :subscribe_configs, required: false, multi: true do
51
- config_param :channels, :array
52
- config_param :read_existing_events, :bool, default: false
53
- config_param :remote_server, :string, default: nil
54
- config_param :remote_domain, :string, default: nil
55
- config_param :remote_username, :string, default: nil
56
- config_param :remote_password, :string, default: nil, secret: true
57
- end
58
-
59
- config_section :storage do
60
- config_set_default :usage, "bookmarks"
61
- config_set_default :@type, DEFAULT_STORAGE_TYPE
62
- config_set_default :persistent, true
63
- end
64
-
65
- config_section :parse do
66
- config_set_default :@type, 'winevt_xml'
67
- config_set_default :estimate_current_event, false
68
- end
69
-
70
- def initalize
71
- super
72
- @chs = []
73
- @keynames = []
74
- end
75
-
76
- def configure(conf)
77
- super
78
- @session = nil
79
- @chs = []
80
- @subscriptions = {}
81
- @all_chs = Winevt::EventLog::Channel.new
82
- @all_chs.force_enumerate = false
83
- @timers = {}
84
-
85
- if @read_all_channels
86
- @all_chs.each do |ch|
87
- uch = ch.strip.downcase
88
- @chs.push([uch, @read_existing_events])
89
- end
90
- end
91
-
92
- @read_existing_events = @read_from_head || @read_existing_events
93
- if @channels.empty? && @subscribe_configs.empty? && !@read_all_channels
94
- @chs.push(['application', @read_existing_events, nil])
95
- else
96
- @channels.map {|ch| ch.strip.downcase }.uniq.each do |uch|
97
- @chs.push([uch, @read_existing_events, nil])
98
- end
99
- @subscribe_configs.each do |subscribe|
100
- if subscribe.remote_server
101
- @session = Winevt::EventLog::Session.new(subscribe.remote_server,
102
- subscribe.remote_domain,
103
- subscribe.remote_username,
104
- subscribe.remote_password)
105
-
106
- log.debug("connect to remote box (server: #{subscribe.remote_server}) domain: #{subscribe.remote_domain} username: #{subscribe.remote_username})")
107
- end
108
- subscribe.channels.map {|ch| ch.strip.downcase }.uniq.each do |uch|
109
- @chs.push([uch, subscribe.read_existing_events, @session])
110
- end
111
- end
112
- end
113
- @chs.uniq!
114
- @keynames = @keys.map {|k| k.strip }.uniq
115
- if @keynames.empty?
116
- @keynames = KEY_MAP.keys
117
- end
118
-
119
- @tag = tag
120
- @bookmarks_storage = storage_create(usage: "bookmarks")
121
- @winevt_xml = false
122
- @parser = nil
123
- if @render_as_xml
124
- @parser = parser_create
125
- @winevt_xml = @parser.respond_to?(:winevt_xml?) && @parser.winevt_xml?
126
- class << self
127
- alias_method :on_notify, :on_notify_xml
128
- end
129
- else
130
- class << self
131
- alias_method :on_notify, :on_notify_hash
132
- end
133
- end
134
-
135
- if @render_as_xml && @preserve_qualifiers_on_hash
136
- raise Fluent::ConfigError, "preserve_qualifiers_on_hash must be used with Hash object rendering(render_as_xml as false)."
137
- end
138
- if !@render_as_xml && !@preserve_qualifiers_on_hash
139
- @keynames.delete('Qualifiers')
140
- elsif @parser.respond_to?(:preserve_qualifiers?) && !@parser.preserve_qualifiers?
141
- @keynames.delete('Qualifiers')
142
- end
143
- @keynames.delete('EventData') if @parse_description
144
-
145
- locale = Winevt::EventLog::Locale.new
146
- if @description_locale && unsupported_locale?(locale, @description_locale)
147
- raise Fluent::ConfigError, "'#{@description_locale}' is not supported. Supported locales are: #{locale.each.map{|code, _desc| code}.join(" ")}"
148
- end
149
- end
150
-
151
- def unsupported_locale?(locale, description_locale)
152
- locale.each.select {|c, _d| c.downcase == description_locale.downcase}.empty?
153
- end
154
-
155
- def start
156
- super
157
-
158
- refresh_subscriptions
159
- if @refresh_subscription_interval
160
- timer_execute(:in_windows_eventlog_refresh_subscription_timer, @refresh_subscription_interval, &method(:refresh_subscriptions))
161
- end
162
- end
163
-
164
- def shutdown
165
- super
166
-
167
- @subscriptions.keys.each do |ch|
168
- subscription = @subscriptions.delete(ch)
169
- if subscription
170
- subscription.cancel
171
- log.debug "channel (#{ch}) subscription is canceled."
172
- end
173
- end
174
- end
175
-
176
- def retry_on_error(channel, times: 15)
177
- try = 0
178
- begin
179
- log.debug "Retry to subscribe for #{channel}...." if try > 1
180
- try += 1
181
- yield
182
- log.info "Retry to subscribe for #{channel} succeeded." if try > 1
183
- try = 0
184
- rescue Winevt::EventLog::Subscribe::RemoteHandlerError => e
185
- raise ReconnectError, "Retrying limit is exceeded." if try > times
186
- log.warn "#{e.message}. Remaining retry count(s): #{times - try}"
187
- sleep 2**try
188
- retry
189
- end
190
- end
191
-
192
- def refresh_subscriptions
193
- clear_subscritpions
194
-
195
- @chs.each do |ch, read_existing_events, session|
196
- retry_on_error(ch) do
197
- ch, subscribe = subscription(ch, read_existing_events, session)
198
- @subscriptions[ch] = subscribe
199
- end
200
- end
201
- subscribe_channels(@subscriptions)
202
- end
203
-
204
- def clear_subscritpions
205
- @subscriptions.keys.each do |ch|
206
- subscription = @subscriptions.delete(ch)
207
- if subscription
208
- if subscription.cancel
209
- log.debug "channel (#{ch}) subscription is cancelled."
210
- subscription.close
211
- log.debug "channel (#{ch}) subscription handles are closed forcibly."
212
- end
213
- end
214
- end
215
- @timers.keys.each do |ch|
216
- timer = @timers.delete(ch)
217
- if timer
218
- event_loop_detach(timer)
219
- log.debug "channel (#{ch}) subscription watcher is detached."
220
- end
221
- end
222
- end
223
-
224
- def subscription(ch, read_existing_events, remote_session)
225
- bookmarkXml = @bookmarks_storage.get(ch) || ""
226
- bookmark = nil
227
- if bookmark_validator(bookmarkXml, ch)
228
- bookmark = Winevt::EventLog::Bookmark.new(bookmarkXml)
229
- end
230
- subscribe = Winevt::EventLog::Subscribe.new
231
- subscribe.read_existing_events = read_existing_events
232
- begin
233
- subscribe.subscribe(ch, "*", bookmark, remote_session)
234
- if !@render_as_xml && @preserve_qualifiers_on_hash
235
- subscribe.preserve_qualifiers = @preserve_qualifiers_on_hash
236
- end
237
- rescue Winevt::EventLog::Query::Error => e
238
- raise Fluent::ConfigError, "Invalid Bookmark XML is loaded. #{e}"
239
- end
240
- subscribe.render_as_xml = @render_as_xml
241
- subscribe.rate_limit = @rate_limit
242
- subscribe.locale = @description_locale if @description_locale
243
- [ch, subscribe]
244
- end
245
-
246
- def subscribe_channels(subscriptions)
247
- subscriptions.each do |ch, subscribe|
248
- @timers[ch] = timer_execute("in_windows_eventlog_#{escape_channel(ch)}".to_sym, @read_interval) do
249
- on_notify(ch, subscribe)
250
- end
251
- log.debug "channel (#{ch}) subscription is subscribed."
252
- end
253
- end
254
-
255
- def bookmark_validator(bookmarkXml, channel)
256
- return false if bookmarkXml.empty?
257
-
258
- evtxml = WinevtBookmarkDocument.new
259
- parser = Nokogiri::XML::SAX::Parser.new(evtxml)
260
- parser.parse(bookmarkXml)
261
- result = evtxml.result
262
- if !result.empty? && (result[:channel].downcase == channel.downcase) && result[:is_current]
263
- true
264
- else
265
- log.warn "This stored bookmark is incomplete for using. Referring `read_existing_events` parameter to subscribe: #{bookmarkXml}, channel: #{channel}"
266
- false
267
- end
268
- end
269
-
270
- def escape_channel(ch)
271
- ch.gsub(/[^a-zA-Z0-9\s]/, '_')
272
- end
273
-
274
- def on_notify(ch, subscribe)
275
- # for safety.
276
- end
277
-
278
- def on_notify_xml(ch, subscribe)
279
- es = Fluent::MultiEventStream.new
280
- begin
281
- subscribe.each do |xml, message, string_inserts|
282
- @parser.parse(xml) do |time, record|
283
- # record.has_key?("EventData") for none parser checking.
284
- if @winevt_xml
285
- record["Description"] = message
286
- record["EventData"] = string_inserts
287
-
288
- h = {}
289
- @keynames.each do |k|
290
- type = KEY_MAP[k][1]
291
- value = record[KEY_MAP[k][0]]
292
- h[k]=case type
293
- when :string
294
- value.to_s
295
- when :array
296
- value.map {|v| v.to_s}
297
- else
298
- raise "Unknown value type: #{type}"
299
- end
300
- end
301
- parse_desc(h) if @parse_description
302
- es.add(Fluent::Engine.now, h)
303
- else
304
- record["Description"] = message
305
- record["EventData"] = string_inserts
306
- # for none parser
307
- es.add(Fluent::Engine.now, record)
308
- end
309
- end
310
- end
311
- router.emit_stream(@tag, es)
312
- @bookmarks_storage.put(ch, subscribe.bookmark)
313
- rescue Winevt::EventLog::Query::Error => e
314
- log.warn "Invalid XML data on #{ch}.", error: e
315
- log.warn_backtrace
316
- end
317
- end
318
-
319
- def on_notify_hash(ch, subscribe)
320
- es = Fluent::MultiEventStream.new
321
- begin
322
- subscribe.each do |record, message, string_inserts|
323
- record["Description"] = message
324
- record["EventData"] = string_inserts
325
- h = {}
326
- @keynames.each do |k|
327
- type = KEY_MAP[k][1]
328
- value = record[KEY_MAP[k][0]]
329
- h[k]=case type
330
- when :string
331
- value.to_s
332
- when :array
333
- value.map {|v| v.to_s}
334
- else
335
- raise "Unknown value type: #{type}"
336
- end
337
- end
338
- parse_desc(h) if @parse_description
339
- es.add(Fluent::Engine.now, h)
340
- end
341
- router.emit_stream(@tag, es)
342
- @bookmarks_storage.put(ch, subscribe.bookmark)
343
- rescue Winevt::EventLog::Query::Error => e
344
- log.warn "Invalid Hash data on #{ch}.", error: e
345
- log.warn_backtrace
346
- end
347
- end
348
-
349
- #### These lines copied from in_windows_eventlog plugin:
350
- #### https://github.com/fluent/fluent-plugin-windows-eventlog/blob/528290d896a885c7721f850943daa3a43a015f3d/lib/fluent/plugin/in_windows_eventlog.rb#L192-L232
351
- GROUP_DELIMITER = "\r\n\r\n".freeze
352
- RECORD_DELIMITER = "\r\n\t".freeze
353
- FIELD_DELIMITER = "\t\t".freeze
354
- NONE_FIELD_DELIMITER = "\t".freeze
355
-
356
- def parse_desc(record)
357
- desc = record.delete("Description".freeze)
358
- return if desc.nil?
359
-
360
- elems = desc.split(GROUP_DELIMITER)
361
- record['DescriptionTitle'] = elems.shift
362
- previous_key = nil
363
- elems.each { |elem|
364
- parent_key = nil
365
- elem.split(RECORD_DELIMITER).each { |r|
366
- key, value = if r.index(FIELD_DELIMITER)
367
- r.split(FIELD_DELIMITER)
368
- else
369
- r.split(NONE_FIELD_DELIMITER)
370
- end
371
- key = "" if key.nil?
372
- key.chop! # remove ':' from key
373
- if value.nil?
374
- parent_key = to_key(key)
375
- else
376
- # parsed value sometimes contain unexpected "\t". So remove it.
377
- value.strip!
378
- # merge empty key values into the previous non-empty key record.
379
- if key.empty?
380
- record[previous_key] = [record[previous_key], value].flatten.reject {|e| e.nil?}
381
- elsif parent_key.nil?
382
- record[to_key(key)] = value
383
- else
384
- k = "#{parent_key}.#{to_key(key)}"
385
- record[k] = value
386
- end
387
- end
388
- # XXX: This is for empty privileges record key.
389
- # We should investigate whether an another case exists or not.
390
- previous_key = to_key(key) unless key.empty?
391
- }
392
- }
393
- end
394
-
395
- def to_key(key)
396
- key.downcase!
397
- key.gsub!(' '.freeze, '_'.freeze)
398
- key
399
- end
400
- ####
401
- end
402
- end
1
+ require 'winevt'
2
+ require 'fluent/plugin/input'
3
+ require 'fluent/plugin'
4
+ require_relative 'bookmark_sax_parser'
5
+
6
+ module Fluent::Plugin
7
+ class WindowsEventLog2Input < Input
8
+ Fluent::Plugin.register_input('windows_eventlog2', self)
9
+
10
+ class ReconnectError < Fluent::UnrecoverableError; end
11
+
12
+ helpers :timer, :storage, :parser
13
+
14
+ DEFAULT_STORAGE_TYPE = 'local'
15
+ KEY_MAP = {"ProviderName" => ["ProviderName", :string],
16
+ "ProviderGUID" => ["ProviderGUID", :string],
17
+ "EventID" => ["EventID", :string],
18
+ "Qualifiers" => ["Qualifiers", :string],
19
+ "Level" => ["Level", :string],
20
+ "Task" => ["Task", :string],
21
+ "Opcode" => ["Opcode", :string],
22
+ "Keywords" => ["Keywords", :string],
23
+ "TimeCreated" => ["TimeCreated", :string],
24
+ "EventRecordID" => ["EventRecordID", :string],
25
+ "ActivityID" => ["ActivityID", :string],
26
+ "RelatedActivityID" => ["RelatedActivityID", :string],
27
+ "ProcessID" => ["ProcessID", :string],
28
+ "ThreadID" => ["ThreadID", :string],
29
+ "Channel" => ["Channel", :string],
30
+ "Computer" => ["Computer", :string],
31
+ "UserID" => ["UserID", :string],
32
+ "Version" => ["Version", :string],
33
+ "Description" => ["Description", :string],
34
+ "EventData" => ["EventData", :array]}
35
+
36
+ config_param :tag, :string
37
+ config_param :read_interval, :time, default: 2
38
+ config_param :channels, :array, default: []
39
+ config_param :keys, :array, default: []
40
+ config_param :read_from_head, :bool, default: false, deprecated: "Use `read_existing_events' instead."
41
+ config_param :read_existing_events, :bool, default: false
42
+ config_param :parse_description, :bool, default: false
43
+ config_param :render_as_xml, :bool, default: false
44
+ config_param :rate_limit, :integer, default: Winevt::EventLog::Subscribe::RATE_INFINITE
45
+ config_param :preserve_qualifiers_on_hash, :bool, default: false
46
+ config_param :read_all_channels, :bool, default: false
47
+ config_param :description_locale, :string, default: nil
48
+ config_param :refresh_subscription_interval, :time, default: nil
49
+ config_param :event_query, :string, default: "*"
50
+
51
+ config_section :subscribe, param_name: :subscribe_configs, required: false, multi: true do
52
+ config_param :channels, :array
53
+ config_param :read_existing_events, :bool, default: false
54
+ config_param :remote_server, :string, default: nil
55
+ config_param :remote_domain, :string, default: nil
56
+ config_param :remote_username, :string, default: nil
57
+ config_param :remote_password, :string, default: nil, secret: true
58
+ end
59
+
60
+ config_section :storage do
61
+ config_set_default :usage, "bookmarks"
62
+ config_set_default :@type, DEFAULT_STORAGE_TYPE
63
+ config_set_default :persistent, true
64
+ end
65
+
66
+ config_section :parse do
67
+ config_set_default :@type, 'winevt_xml'
68
+ config_set_default :estimate_current_event, false
69
+ end
70
+
71
+ def initalize
72
+ super
73
+ @chs = []
74
+ @keynames = []
75
+ end
76
+
77
+ def configure(conf)
78
+ super
79
+ @session = nil
80
+ @chs = []
81
+ @subscriptions = {}
82
+ @all_chs = Winevt::EventLog::Channel.new
83
+ @all_chs.force_enumerate = false
84
+ @timers = {}
85
+
86
+ if @read_all_channels
87
+ @all_chs.each do |ch|
88
+ uch = ch.strip.downcase
89
+ @chs.push([uch, @read_existing_events])
90
+ end
91
+ end
92
+
93
+ @read_existing_events = @read_from_head || @read_existing_events
94
+ if @channels.empty? && @subscribe_configs.empty? && !@read_all_channels
95
+ @chs.push(['application', @read_existing_events, nil])
96
+ else
97
+ @channels.map {|ch| ch.strip.downcase }.uniq.each do |uch|
98
+ @chs.push([uch, @read_existing_events, nil])
99
+ end
100
+ @subscribe_configs.each do |subscribe|
101
+ if subscribe.remote_server
102
+ @session = Winevt::EventLog::Session.new(subscribe.remote_server,
103
+ subscribe.remote_domain,
104
+ subscribe.remote_username,
105
+ subscribe.remote_password)
106
+
107
+ log.debug("connect to remote box (server: #{subscribe.remote_server}) domain: #{subscribe.remote_domain} username: #{subscribe.remote_username})")
108
+ end
109
+ subscribe.channels.map {|ch| ch.strip.downcase }.uniq.each do |uch|
110
+ @chs.push([uch, subscribe.read_existing_events, @session])
111
+ end
112
+ end
113
+ end
114
+ @chs.uniq!
115
+ @keynames = @keys.map {|k| k.strip }.uniq
116
+ if @keynames.empty?
117
+ @keynames = KEY_MAP.keys
118
+ end
119
+
120
+ @tag = tag
121
+ @bookmarks_storage = storage_create(usage: "bookmarks")
122
+ @winevt_xml = false
123
+ @parser = nil
124
+ if @render_as_xml
125
+ @parser = parser_create
126
+ @winevt_xml = @parser.respond_to?(:winevt_xml?) && @parser.winevt_xml?
127
+ class << self
128
+ alias_method :on_notify, :on_notify_xml
129
+ end
130
+ else
131
+ class << self
132
+ alias_method :on_notify, :on_notify_hash
133
+ end
134
+ end
135
+
136
+ if @render_as_xml && @preserve_qualifiers_on_hash
137
+ raise Fluent::ConfigError, "preserve_qualifiers_on_hash must be used with Hash object rendering(render_as_xml as false)."
138
+ end
139
+ if !@render_as_xml && !@preserve_qualifiers_on_hash
140
+ @keynames.delete('Qualifiers')
141
+ elsif @parser.respond_to?(:preserve_qualifiers?) && !@parser.preserve_qualifiers?
142
+ @keynames.delete('Qualifiers')
143
+ end
144
+ @keynames.delete('EventData') if @parse_description
145
+
146
+ locale = Winevt::EventLog::Locale.new
147
+ if @description_locale && unsupported_locale?(locale, @description_locale)
148
+ raise Fluent::ConfigError, "'#{@description_locale}' is not supported. Supported locales are: #{locale.each.map{|code, _desc| code}.join(" ")}"
149
+ end
150
+ end
151
+
152
+ def unsupported_locale?(locale, description_locale)
153
+ locale.each.select {|c, _d| c.downcase == description_locale.downcase}.empty?
154
+ end
155
+
156
+ def start
157
+ super
158
+
159
+ refresh_subscriptions
160
+ if @refresh_subscription_interval
161
+ timer_execute(:in_windows_eventlog_refresh_subscription_timer, @refresh_subscription_interval, &method(:refresh_subscriptions))
162
+ end
163
+ end
164
+
165
+ def shutdown
166
+ super
167
+
168
+ @subscriptions.keys.each do |ch|
169
+ subscription = @subscriptions.delete(ch)
170
+ if subscription
171
+ subscription.cancel
172
+ log.debug "channel (#{ch}) subscription is canceled."
173
+ end
174
+ end
175
+ end
176
+
177
+ def retry_on_error(channel, times: 15)
178
+ try = 0
179
+ begin
180
+ log.debug "Retry to subscribe for #{channel}...." if try > 1
181
+ try += 1
182
+ yield
183
+ log.info "Retry to subscribe for #{channel} succeeded." if try > 1
184
+ try = 0
185
+ rescue Winevt::EventLog::Subscribe::RemoteHandlerError => e
186
+ raise ReconnectError, "Retrying limit is exceeded." if try > times
187
+ log.warn "#{e.message}. Remaining retry count(s): #{times - try}"
188
+ sleep 2**try
189
+ retry
190
+ end
191
+ end
192
+
193
+ def refresh_subscriptions
194
+ clear_subscritpions
195
+
196
+ @chs.each do |ch, read_existing_events, session|
197
+ retry_on_error(ch) do
198
+ begin
199
+ ch, subscribe = subscription(ch, read_existing_events, session)
200
+ @subscriptions[ch] = subscribe
201
+ rescue Winevt::EventLog::ChannelNotFoundError => e
202
+ log.warn "#{e.message}"
203
+ end
204
+ end
205
+ end
206
+ subscribe_channels(@subscriptions)
207
+ end
208
+
209
+ def clear_subscritpions
210
+ @subscriptions.keys.each do |ch|
211
+ subscription = @subscriptions.delete(ch)
212
+ if subscription
213
+ if subscription.cancel
214
+ log.debug "channel (#{ch}) subscription is cancelled."
215
+ subscription.close
216
+ log.debug "channel (#{ch}) subscription handles are closed forcibly."
217
+ end
218
+ end
219
+ end
220
+ @timers.keys.each do |ch|
221
+ timer = @timers.delete(ch)
222
+ if timer
223
+ event_loop_detach(timer)
224
+ log.debug "channel (#{ch}) subscription watcher is detached."
225
+ end
226
+ end
227
+ end
228
+
229
+ def subscription(ch, read_existing_events, remote_session)
230
+ bookmarkXml = @bookmarks_storage.get(ch) || ""
231
+ bookmark = nil
232
+ if bookmark_validator(bookmarkXml, ch)
233
+ bookmark = Winevt::EventLog::Bookmark.new(bookmarkXml)
234
+ end
235
+ subscribe = Winevt::EventLog::Subscribe.new
236
+ subscribe.read_existing_events = read_existing_events
237
+ begin
238
+ subscribe.subscribe(ch, event_query, bookmark, remote_session)
239
+ if !@render_as_xml && @preserve_qualifiers_on_hash
240
+ subscribe.preserve_qualifiers = @preserve_qualifiers_on_hash
241
+ end
242
+ rescue Winevt::EventLog::Query::Error => e
243
+ raise Fluent::ConfigError, "Invalid Bookmark XML is loaded. #{e}"
244
+ end
245
+ subscribe.render_as_xml = @render_as_xml
246
+ subscribe.rate_limit = @rate_limit
247
+ subscribe.locale = @description_locale if @description_locale
248
+ [ch, subscribe]
249
+ end
250
+
251
+ def subscribe_channels(subscriptions)
252
+ subscriptions.each do |ch, subscribe|
253
+ log.trace "Subscribing Windows EventLog at #{ch} channel"
254
+ @timers[ch] = timer_execute("in_windows_eventlog_#{escape_channel(ch)}".to_sym, @read_interval) do
255
+ on_notify(ch, subscribe)
256
+ end
257
+ log.debug "channel (#{ch}) subscription is subscribed."
258
+ end
259
+ end
260
+
261
+ def bookmark_validator(bookmarkXml, channel)
262
+ return false if bookmarkXml.empty?
263
+
264
+ evtxml = WinevtBookmarkDocument.new
265
+ parser = Nokogiri::XML::SAX::Parser.new(evtxml)
266
+ parser.parse(bookmarkXml)
267
+ result = evtxml.result
268
+ if !result.empty? && (result[:channel].downcase == channel.downcase) && result[:is_current]
269
+ true
270
+ else
271
+ log.warn "This stored bookmark is incomplete for using. Referring `read_existing_events` parameter to subscribe: #{bookmarkXml}, channel: #{channel}"
272
+ false
273
+ end
274
+ end
275
+
276
+ def escape_channel(ch)
277
+ ch.gsub(/[^a-zA-Z0-9\s]/, '_')
278
+ end
279
+
280
+ def on_notify(ch, subscribe)
281
+ # for safety.
282
+ end
283
+
284
+ def on_notify_xml(ch, subscribe)
285
+ es = Fluent::MultiEventStream.new
286
+ begin
287
+ subscribe.each do |xml, message, string_inserts|
288
+ @parser.parse(xml) do |time, record|
289
+ # record.has_key?("EventData") for none parser checking.
290
+ if @winevt_xml
291
+ record["Description"] = message
292
+ record["EventData"] = string_inserts
293
+
294
+ h = {}
295
+ @keynames.each do |k|
296
+ type = KEY_MAP[k][1]
297
+ value = record[KEY_MAP[k][0]]
298
+ h[k]=case type
299
+ when :string
300
+ value.to_s
301
+ when :array
302
+ value.map {|v| v.to_s}
303
+ else
304
+ raise "Unknown value type: #{type}"
305
+ end
306
+ end
307
+ parse_desc(h) if @parse_description
308
+ es.add(Fluent::Engine.now, h)
309
+ else
310
+ record["Description"] = message
311
+ record["EventData"] = string_inserts
312
+ # for none parser
313
+ es.add(Fluent::Engine.now, record)
314
+ end
315
+ end
316
+ end
317
+ router.emit_stream(@tag, es)
318
+ @bookmarks_storage.put(ch, subscribe.bookmark)
319
+ log.trace "Collecting Windows EventLog from #{ch} channel. Collected size: #{es.size}"
320
+ rescue Winevt::EventLog::Query::Error => e
321
+ log.warn "Invalid XML data on #{ch}.", error: e
322
+ log.warn_backtrace
323
+ end
324
+ end
325
+
326
+ def on_notify_hash(ch, subscribe)
327
+ es = Fluent::MultiEventStream.new
328
+ begin
329
+ subscribe.each do |record, message, string_inserts|
330
+ record["Description"] = message
331
+ record["EventData"] = string_inserts
332
+ h = {}
333
+ @keynames.each do |k|
334
+ type = KEY_MAP[k][1]
335
+ value = record[KEY_MAP[k][0]]
336
+ h[k]=case type
337
+ when :string
338
+ value.to_s
339
+ when :array
340
+ value.map {|v| v.to_s}
341
+ else
342
+ raise "Unknown value type: #{type}"
343
+ end
344
+ end
345
+ parse_desc(h) if @parse_description
346
+ es.add(Fluent::Engine.now, h)
347
+ end
348
+ router.emit_stream(@tag, es)
349
+ @bookmarks_storage.put(ch, subscribe.bookmark)
350
+ log.trace "Collecting Windows EventLog from #{ch} channel. Collected size: #{es.size}"
351
+ rescue Winevt::EventLog::Query::Error => e
352
+ log.warn "Invalid Hash data on #{ch}.", error: e
353
+ log.warn_backtrace
354
+ end
355
+ end
356
+
357
+ #### These lines copied from in_windows_eventlog plugin:
358
+ #### https://github.com/fluent/fluent-plugin-windows-eventlog/blob/528290d896a885c7721f850943daa3a43a015f3d/lib/fluent/plugin/in_windows_eventlog.rb#L192-L232
359
+ GROUP_DELIMITER = "\r\n\r\n".freeze
360
+ RECORD_DELIMITER = "\r\n\t".freeze
361
+ FIELD_DELIMITER = "\t\t".freeze
362
+ NONE_FIELD_DELIMITER = "\t".freeze
363
+
364
+ def parse_desc(record)
365
+ desc = record.delete("Description".freeze)
366
+ return if desc.nil?
367
+
368
+ elems = desc.split(GROUP_DELIMITER)
369
+ record['DescriptionTitle'] = elems.shift
370
+ previous_key = nil
371
+ elems.each { |elem|
372
+ parent_key = nil
373
+ elem.split(RECORD_DELIMITER).each { |r|
374
+ key, value = if r.index(FIELD_DELIMITER)
375
+ r.split(FIELD_DELIMITER)
376
+ else
377
+ r.split(NONE_FIELD_DELIMITER)
378
+ end
379
+ key = "" if key.nil?
380
+ key.chop! # remove ':' from key
381
+ if value.nil?
382
+ parent_key = to_key(key)
383
+ else
384
+ # parsed value sometimes contain unexpected "\t". So remove it.
385
+ value.strip!
386
+ # merge empty key values into the previous non-empty key record.
387
+ if key.empty?
388
+ record[previous_key] = [record[previous_key], value].flatten.reject {|e| e.nil?}
389
+ elsif parent_key.nil?
390
+ record[to_key(key)] = value
391
+ else
392
+ k = "#{parent_key}.#{to_key(key)}"
393
+ record[k] = value
394
+ end
395
+ end
396
+ # XXX: This is for empty privileges record key.
397
+ # We should investigate whether an another case exists or not.
398
+ previous_key = to_key(key) unless key.empty?
399
+ }
400
+ }
401
+ end
402
+
403
+ def to_key(key)
404
+ key.downcase!
405
+ key.gsub!(' '.freeze, '_'.freeze)
406
+ key
407
+ end
408
+ ####
409
+ end
410
+ end