fluent-plugin-windows-eventlog 0.8.1 → 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,406 +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
- 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
- ch, subscribe = subscription(ch, read_existing_events, session)
199
- @subscriptions[ch] = subscribe
200
- end
201
- end
202
- subscribe_channels(@subscriptions)
203
- end
204
-
205
- def clear_subscritpions
206
- @subscriptions.keys.each do |ch|
207
- subscription = @subscriptions.delete(ch)
208
- if subscription
209
- if subscription.cancel
210
- log.debug "channel (#{ch}) subscription is cancelled."
211
- subscription.close
212
- log.debug "channel (#{ch}) subscription handles are closed forcibly."
213
- end
214
- end
215
- end
216
- @timers.keys.each do |ch|
217
- timer = @timers.delete(ch)
218
- if timer
219
- event_loop_detach(timer)
220
- log.debug "channel (#{ch}) subscription watcher is detached."
221
- end
222
- end
223
- end
224
-
225
- def subscription(ch, read_existing_events, remote_session)
226
- bookmarkXml = @bookmarks_storage.get(ch) || ""
227
- bookmark = nil
228
- if bookmark_validator(bookmarkXml, ch)
229
- bookmark = Winevt::EventLog::Bookmark.new(bookmarkXml)
230
- end
231
- subscribe = Winevt::EventLog::Subscribe.new
232
- subscribe.read_existing_events = read_existing_events
233
- begin
234
- subscribe.subscribe(ch, event_query, bookmark, remote_session)
235
- if !@render_as_xml && @preserve_qualifiers_on_hash
236
- subscribe.preserve_qualifiers = @preserve_qualifiers_on_hash
237
- end
238
- rescue Winevt::EventLog::Query::Error => e
239
- raise Fluent::ConfigError, "Invalid Bookmark XML is loaded. #{e}"
240
- end
241
- subscribe.render_as_xml = @render_as_xml
242
- subscribe.rate_limit = @rate_limit
243
- subscribe.locale = @description_locale if @description_locale
244
- [ch, subscribe]
245
- end
246
-
247
- def subscribe_channels(subscriptions)
248
- subscriptions.each do |ch, subscribe|
249
- log.trace "Subscribing Windows EventLog at #{ch} channel"
250
- @timers[ch] = timer_execute("in_windows_eventlog_#{escape_channel(ch)}".to_sym, @read_interval) do
251
- on_notify(ch, subscribe)
252
- end
253
- log.debug "channel (#{ch}) subscription is subscribed."
254
- end
255
- end
256
-
257
- def bookmark_validator(bookmarkXml, channel)
258
- return false if bookmarkXml.empty?
259
-
260
- evtxml = WinevtBookmarkDocument.new
261
- parser = Nokogiri::XML::SAX::Parser.new(evtxml)
262
- parser.parse(bookmarkXml)
263
- result = evtxml.result
264
- if !result.empty? && (result[:channel].downcase == channel.downcase) && result[:is_current]
265
- true
266
- else
267
- log.warn "This stored bookmark is incomplete for using. Referring `read_existing_events` parameter to subscribe: #{bookmarkXml}, channel: #{channel}"
268
- false
269
- end
270
- end
271
-
272
- def escape_channel(ch)
273
- ch.gsub(/[^a-zA-Z0-9\s]/, '_')
274
- end
275
-
276
- def on_notify(ch, subscribe)
277
- # for safety.
278
- end
279
-
280
- def on_notify_xml(ch, subscribe)
281
- es = Fluent::MultiEventStream.new
282
- begin
283
- subscribe.each do |xml, message, string_inserts|
284
- @parser.parse(xml) do |time, record|
285
- # record.has_key?("EventData") for none parser checking.
286
- if @winevt_xml
287
- record["Description"] = message
288
- record["EventData"] = string_inserts
289
-
290
- h = {}
291
- @keynames.each do |k|
292
- type = KEY_MAP[k][1]
293
- value = record[KEY_MAP[k][0]]
294
- h[k]=case type
295
- when :string
296
- value.to_s
297
- when :array
298
- value.map {|v| v.to_s}
299
- else
300
- raise "Unknown value type: #{type}"
301
- end
302
- end
303
- parse_desc(h) if @parse_description
304
- es.add(Fluent::Engine.now, h)
305
- else
306
- record["Description"] = message
307
- record["EventData"] = string_inserts
308
- # for none parser
309
- es.add(Fluent::Engine.now, record)
310
- end
311
- end
312
- end
313
- router.emit_stream(@tag, es)
314
- @bookmarks_storage.put(ch, subscribe.bookmark)
315
- log.trace "Collecting Windows EventLog from #{ch} channel. Collected size: #{es.size}"
316
- rescue Winevt::EventLog::Query::Error => e
317
- log.warn "Invalid XML data on #{ch}.", error: e
318
- log.warn_backtrace
319
- end
320
- end
321
-
322
- def on_notify_hash(ch, subscribe)
323
- es = Fluent::MultiEventStream.new
324
- begin
325
- subscribe.each do |record, message, string_inserts|
326
- record["Description"] = message
327
- record["EventData"] = string_inserts
328
- h = {}
329
- @keynames.each do |k|
330
- type = KEY_MAP[k][1]
331
- value = record[KEY_MAP[k][0]]
332
- h[k]=case type
333
- when :string
334
- value.to_s
335
- when :array
336
- value.map {|v| v.to_s}
337
- else
338
- raise "Unknown value type: #{type}"
339
- end
340
- end
341
- parse_desc(h) if @parse_description
342
- es.add(Fluent::Engine.now, h)
343
- end
344
- router.emit_stream(@tag, es)
345
- @bookmarks_storage.put(ch, subscribe.bookmark)
346
- log.trace "Collecting Windows EventLog from #{ch} channel. Collected size: #{es.size}"
347
- rescue Winevt::EventLog::Query::Error => e
348
- log.warn "Invalid Hash data on #{ch}.", error: e
349
- log.warn_backtrace
350
- end
351
- end
352
-
353
- #### These lines copied from in_windows_eventlog plugin:
354
- #### https://github.com/fluent/fluent-plugin-windows-eventlog/blob/528290d896a885c7721f850943daa3a43a015f3d/lib/fluent/plugin/in_windows_eventlog.rb#L192-L232
355
- GROUP_DELIMITER = "\r\n\r\n".freeze
356
- RECORD_DELIMITER = "\r\n\t".freeze
357
- FIELD_DELIMITER = "\t\t".freeze
358
- NONE_FIELD_DELIMITER = "\t".freeze
359
-
360
- def parse_desc(record)
361
- desc = record.delete("Description".freeze)
362
- return if desc.nil?
363
-
364
- elems = desc.split(GROUP_DELIMITER)
365
- record['DescriptionTitle'] = elems.shift
366
- previous_key = nil
367
- elems.each { |elem|
368
- parent_key = nil
369
- elem.split(RECORD_DELIMITER).each { |r|
370
- key, value = if r.index(FIELD_DELIMITER)
371
- r.split(FIELD_DELIMITER)
372
- else
373
- r.split(NONE_FIELD_DELIMITER)
374
- end
375
- key = "" if key.nil?
376
- key.chop! # remove ':' from key
377
- if value.nil?
378
- parent_key = to_key(key)
379
- else
380
- # parsed value sometimes contain unexpected "\t". So remove it.
381
- value.strip!
382
- # merge empty key values into the previous non-empty key record.
383
- if key.empty?
384
- record[previous_key] = [record[previous_key], value].flatten.reject {|e| e.nil?}
385
- elsif parent_key.nil?
386
- record[to_key(key)] = value
387
- else
388
- k = "#{parent_key}.#{to_key(key)}"
389
- record[k] = value
390
- end
391
- end
392
- # XXX: This is for empty privileges record key.
393
- # We should investigate whether an another case exists or not.
394
- previous_key = to_key(key) unless key.empty?
395
- }
396
- }
397
- end
398
-
399
- def to_key(key)
400
- key.downcase!
401
- key.gsub!(' '.freeze, '_'.freeze)
402
- key
403
- end
404
- ####
405
- end
406
- 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