fluent-plugin-windows-eventlog 0.8.0 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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