dap 0.0.1

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.
Files changed (54) hide show
  1. data/.gitignore +6 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +15 -0
  4. data/Gemfile.lock +55 -0
  5. data/LICENSE +20 -0
  6. data/README.md +15 -0
  7. data/bin/dap +137 -0
  8. data/dap.gemspec +42 -0
  9. data/data/.gitkeep +0 -0
  10. data/lib/dap.rb +101 -0
  11. data/lib/dap/filter.rb +8 -0
  12. data/lib/dap/filter/base.rb +37 -0
  13. data/lib/dap/filter/geoip.rb +72 -0
  14. data/lib/dap/filter/http.rb +173 -0
  15. data/lib/dap/filter/names.rb +151 -0
  16. data/lib/dap/filter/openssl.rb +53 -0
  17. data/lib/dap/filter/recog.rb +23 -0
  18. data/lib/dap/filter/simple.rb +340 -0
  19. data/lib/dap/filter/udp.rb +401 -0
  20. data/lib/dap/input.rb +74 -0
  21. data/lib/dap/input/csv.rb +60 -0
  22. data/lib/dap/input/warc.rb +81 -0
  23. data/lib/dap/output.rb +117 -0
  24. data/lib/dap/proto/addp.rb +0 -0
  25. data/lib/dap/proto/dtls.rb +21 -0
  26. data/lib/dap/proto/ipmi.rb +94 -0
  27. data/lib/dap/proto/natpmp.rb +19 -0
  28. data/lib/dap/proto/wdbrpc.rb +58 -0
  29. data/lib/dap/utils/oui.rb +16586 -0
  30. data/lib/dap/version.rb +3 -0
  31. data/samples/http_get_reply.ic12.bz2 +0 -0
  32. data/samples/http_get_reply.ic12.sh +1 -0
  33. data/samples/http_get_reply_iframes.json.bz2 +0 -0
  34. data/samples/http_get_reply_iframes.json.sh +1 -0
  35. data/samples/http_get_reply_links.json.sh +1 -0
  36. data/samples/iawide.warc.bz2 +0 -0
  37. data/samples/iawide_warc.sh +1 -0
  38. data/samples/ipmi_chan_auth_replies.crd.bz2 +0 -0
  39. data/samples/ipmi_chan_auth_replies.sh +1 -0
  40. data/samples/ssl_certs.bz2 +0 -0
  41. data/samples/ssl_certs_geo.sh +1 -0
  42. data/samples/ssl_certs_names.sh +1 -0
  43. data/samples/ssl_certs_names_expanded.sh +1 -0
  44. data/samples/ssl_certs_org.sh +1 -0
  45. data/samples/udp-netbios.csv.bz2 +0 -0
  46. data/samples/udp-netbios.sh +1 -0
  47. data/spec/dap/proto/ipmi_spec.rb +19 -0
  48. data/tools/geo-ip-summary.rb +149 -0
  49. data/tools/ipmi-vulns.rb +27 -0
  50. data/tools/json-summarize.rb +81 -0
  51. data/tools/netbios-counts.rb +271 -0
  52. data/tools/upnp-vulns.rb +35 -0
  53. data/tools/value-counts-to-md-table.rb +23 -0
  54. metadata +264 -0
@@ -0,0 +1,23 @@
1
+ require 'recog'
2
+
3
+ module Dap
4
+ module Filter
5
+
6
+ class FilterRecog
7
+ include Base
8
+
9
+ def process(doc)
10
+ self.opts.each_pair do |k,v|
11
+ next unless doc.has_key?(k)
12
+ match = Recog::Nizer.match(v, doc[k])
13
+ next unless match
14
+ match.each_pair do |ok, ov|
15
+ doc["#{k}.recog.#{ok}"] = ov.to_s
16
+ end
17
+ end
18
+ [ doc ]
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,340 @@
1
+ module Dap
2
+ module Filter
3
+
4
+ class FilterRename
5
+ include Base
6
+ def process(doc)
7
+ self.opts.each_pair do |k,v|
8
+ if doc.has_key?(k)
9
+ doc[v] = doc[k]
10
+ doc.delete(k)
11
+ end
12
+ end
13
+ [ doc ]
14
+ end
15
+ end
16
+
17
+ class FilterRemove
18
+ include Base
19
+ def process(doc)
20
+ self.opts.each_pair do |k,v|
21
+ if doc.has_key?(k)
22
+ doc.delete(k)
23
+ end
24
+ end
25
+ [ doc ]
26
+ end
27
+ end
28
+
29
+ class FilterSelect
30
+ include Base
31
+ def process(doc)
32
+ ndoc = {}
33
+ self.opts.each_pair do |k,v|
34
+ if doc.has_key?(k)
35
+ ndoc[k] = doc[k]
36
+ end
37
+ end
38
+ (ndoc.keys.length == 0) ? [] : [ ndoc ]
39
+ end
40
+ end
41
+
42
+ class FilterInsert
43
+ include Base
44
+ def process(doc)
45
+ self.opts.each_pair do |k,v|
46
+ doc[k] = v
47
+ end
48
+ [ doc ]
49
+ end
50
+ end
51
+
52
+ class FilterInclude
53
+ include Base
54
+ def process(doc)
55
+ self.opts.each_pair do |k,v|
56
+ if doc.has_key?(k) and doc[k].to_s.index(v)
57
+ return [ doc ]
58
+ end
59
+ end
60
+ [ ]
61
+ end
62
+ end
63
+
64
+ # where 'some.field == some_value'
65
+ # where 'some.field != some_value'
66
+ # TODO: do something other than basic string comparison. Would be nice to have where 'some.field > 2', etc
67
+ class FilterWhere
68
+ attr_accessor :query
69
+
70
+ def initialize(args)
71
+ fail "Expected 3 arguments to 'where' but got #{args.size}" unless args.size == 3
72
+ self.query = args
73
+ end
74
+
75
+ def process(doc)
76
+ field, operator, expected = self.query
77
+ return [ doc ] if doc.has_key?(field) and doc[field].send(operator, expected)
78
+ [ ]
79
+ end
80
+ end
81
+
82
+ class FilterExclude
83
+ include Base
84
+ def process(doc)
85
+ self.opts.each_pair do |k,v|
86
+ if doc.has_key?(k) and doc[k].to_s.index(v)
87
+ return [ ]
88
+ end
89
+ end
90
+ [ doc ]
91
+ end
92
+ end
93
+
94
+ class FilterExists
95
+ include Base
96
+ def process(doc)
97
+ self.opts.each_pair do |k,v|
98
+ if doc.has_key?(k) and doc[k].to_s.length > 0
99
+ return [ doc ]
100
+ end
101
+ end
102
+ [ ]
103
+ end
104
+ end
105
+
106
+ class FilterNotExists < FilterExists
107
+ include Base
108
+ def process(doc)
109
+ exists_doc = super(doc)
110
+ exists_doc.empty? ? [ doc ] : [ ]
111
+ end
112
+ end
113
+
114
+ # Applies some simple annotation to the given fields, adding another
115
+ # field name with the appended annotation type, i.e.:
116
+ #
117
+ # $ echo '{"foo":"blah"}' | dap json stdin + annotate foo=length + json
118
+ # {"foo":"bar","foo.length":4}
119
+ class FilterAnnotate
120
+ include Base
121
+ def process(doc)
122
+ self.opts.each_pair do |k,v|
123
+ if doc.has_key?(k)
124
+ case v
125
+ when 'length'
126
+ doc["#{k}.length"] = doc[k].length
127
+ when 'size'
128
+ doc["#{k}.size"] = doc[k].size
129
+ else
130
+ fail "Unsupported annotation '#{v}'"
131
+ end
132
+ end
133
+ end
134
+ [ doc ]
135
+ end
136
+ end
137
+
138
+ class FilterTransform
139
+ include Base
140
+ def process(doc)
141
+ self.opts.each_pair do |k,v|
142
+ if doc.has_key?(k)
143
+ case v
144
+ when 'downcase'
145
+ doc[k] = doc[k].to_s.downcase
146
+ when 'upcase'
147
+ doc[k] = doc[k].to_s.upcase
148
+ when 'ascii'
149
+ doc[k] = doc[k].to_s.gsub(/[\x00-\x1f\x7f-\xff]/n, '')
150
+ when 'utf8encode'
151
+ doc[k] = doc[k].to_s.encode!('UTF-8', invalid: :replace, undef: :replace, replace: '')
152
+ when 'base64decode'
153
+ doc[k] = doc[k].to_s.unpack('m*').first
154
+ when 'base64encode'
155
+ doc[k] = [doc[k].to_s].pack('m*').gsub(/\s+/n, '')
156
+ when 'qprintdecode'
157
+ doc[k] = doc[k].to_s.gsub(/=([0-9A-Fa-f]{2})/n){ |x| [x[1,2]].pack("H*") }
158
+ when 'qprintencode'
159
+ doc[k] = doc[k].to_s.gsub(/[\x00-\x20\x3d\x7f-\xff]/n){|x| ( "=%.2x" % x.unpack("C").first ).upcase }
160
+ when 'hexdecode'
161
+ doc[k] = [ doc[k].to_s ].pack("H*")
162
+ when 'hexencode'
163
+ doc[k] = doc[k].to_s.unpack("H*").first
164
+ end
165
+ end
166
+ end
167
+ [ doc ]
168
+ end
169
+ end
170
+
171
+ class FilterTruncate
172
+ include Base
173
+ def process(doc)
174
+ self.opts.each_pair do |k,v|
175
+ if doc.has_key?(k)
176
+ doc[k] = doc[k].to_s[0, v.to_i]
177
+ end
178
+ end
179
+ [ doc ]
180
+ end
181
+ end
182
+
183
+ class FilterSplitLine
184
+ include Base
185
+ def process(doc)
186
+ lines = [ ]
187
+ self.opts.each_pair do |k,v|
188
+ if doc.has_key?(k)
189
+ doc[k].to_s.split(/\n/).each do |line|
190
+ lines << doc.merge({ "#{k}.line" => line })
191
+ end
192
+ end
193
+ end
194
+ lines.length == 0 ? [ doc ] : [ lines ]
195
+ end
196
+ end
197
+
198
+ class FilterSplitWord
199
+ include Base
200
+ def process(doc)
201
+ lines = [ ]
202
+ self.opts.each_pair do |k,v|
203
+ if doc.has_key?(k)
204
+ doc[k].to_s.split(/\W/).each do |line|
205
+ lines << doc.merge({ "#{k}.word" => line })
206
+ end
207
+ end
208
+ end
209
+ lines.length == 0 ? [ doc ] : [ lines ]
210
+ end
211
+ end
212
+
213
+ class FilterSplitTab
214
+ include Base
215
+ def process(doc)
216
+ lines = [ ]
217
+ self.opts.each_pair do |k,v|
218
+ if doc.has_key?(k)
219
+ doc[k].to_s.split(/\t/).each do |line|
220
+ lines << doc.merge({ "#{k}.tab" => line })
221
+ end
222
+ end
223
+ end
224
+ lines.length == 0 ? [ doc ] : [ lines ]
225
+ end
226
+ end
227
+
228
+
229
+ class FilterSplitComma
230
+ include Base
231
+ def process(doc)
232
+ lines = [ ]
233
+ self.opts.each_pair do |k,v|
234
+ if doc.has_key?(k)
235
+ doc[k].to_s.split(/m/).each do |line|
236
+ lines << doc.merge({ "#{k}.word" => line })
237
+ end
238
+ end
239
+ end
240
+ lines.length == 0 ? [ doc ] : [ lines ]
241
+ end
242
+ end
243
+
244
+ class FilterSplitArray
245
+ include Base
246
+ def process(doc)
247
+ lines = [ ]
248
+ self.opts.each_pair do |k,v|
249
+ if doc.has_key?(k) and doc[k].respond_to?(:each)
250
+ doc[k].each do |line|
251
+ lines << doc.merge({ "#{k}.item" => line })
252
+ end
253
+ end
254
+ end
255
+ lines.length == 0 ? [ doc ] : [ lines ]
256
+ end
257
+ end
258
+
259
+ class FilterFieldSplitLine
260
+ include Base
261
+ def process(doc)
262
+ self.opts.each_pair do |k,v|
263
+ if doc.has_key?(k)
264
+ lcount = 1
265
+ doc[k].to_s.split(/\n/).each do |line|
266
+ doc.merge!({ "#{k}.f#{lcount}" => line })
267
+ lcount += 1
268
+ end
269
+ end
270
+ end
271
+ [ doc ]
272
+ end
273
+ end
274
+
275
+ class FilterFieldSplitWord
276
+ include Base
277
+ def process(doc)
278
+ self.opts.each_pair do |k,v|
279
+ if doc.has_key?(k)
280
+ wcount = 1
281
+ doc[k].to_s.split(/\W/).each do |word|
282
+ doc.merge!({ "#{k}.f#{wcount}" => word })
283
+ wcount += 1
284
+ end
285
+ end
286
+ end
287
+ [ doc ]
288
+ end
289
+ end
290
+
291
+ class FilterFieldSplitTab
292
+ include Base
293
+ def process(doc)
294
+ self.opts.each_pair do |k,v|
295
+ if doc.has_key?(k)
296
+ wcount = 1
297
+ doc[k].to_s.split(/\t/).each do |word|
298
+ doc.merge!({ "#{k}.f#{wcount}" => word })
299
+ wcount += 1
300
+ end
301
+ end
302
+ end
303
+ [ doc ]
304
+ end
305
+ end
306
+
307
+ class FilterFieldSplitComma
308
+ include Base
309
+ def process(doc)
310
+ self.opts.each_pair do |k,v|
311
+ if doc.has_key?(k)
312
+ wcount = 1
313
+ doc[k].to_s.split(/,/).each do |word|
314
+ doc.merge!({ "#{k}.f#{wcount}" => word })
315
+ wcount += 1
316
+ end
317
+ end
318
+ end
319
+ [ doc ]
320
+ end
321
+ end
322
+
323
+ class FilterFieldSplitArray
324
+ include Base
325
+ def process(doc)
326
+ self.opts.each_pair do |k,v|
327
+ if doc.has_key?(k) and doc[k].respond_to?(:each)
328
+ wcount = 1
329
+ doc[k].each do |word|
330
+ doc.merge!({ "#{k}.f#{wcount}" => word })
331
+ wcount += 1
332
+ end
333
+ end
334
+ end
335
+ [ doc ]
336
+ end
337
+ end
338
+
339
+ end
340
+ end
@@ -0,0 +1,401 @@
1
+ module Dap
2
+ module Filter
3
+
4
+ require 'openssl'
5
+ require 'net/dns'
6
+ require 'bit-struct'
7
+
8
+ require 'dap/proto/addp'
9
+ require 'dap/proto/dtls'
10
+ require 'dap/proto/natpmp'
11
+ require 'dap/proto/wdbrpc'
12
+ require 'dap/proto/ipmi'
13
+ require 'dap/utils/oui'
14
+
15
+ #
16
+ # Decode a MDNS Services probe response ( zmap: mdns_5353.pkt )
17
+ #
18
+ class FilterDecodeMDNSSrvReply
19
+ include BaseDecoder
20
+ def decode(data)
21
+ begin
22
+ r = Net::DNS::Packet.parse(data)
23
+ return if not r
24
+
25
+ # XXX: This can throw an exception on bad data
26
+ svcs = r.answer.map {|x| (x.value.to_s) }
27
+ svcs.delete('')
28
+ return if not (svcs and svcs.length > 0)
29
+ return { "mdns_services" => svc.join(" ") }
30
+ rescue ::Exception
31
+ end
32
+ nil
33
+ end
34
+ end
35
+
36
+ #
37
+ # Decode a DNS bind.version probe response ( zmap: dns_53.pkt )
38
+ #
39
+ class FilterDecodeDNSVersionReply
40
+ include BaseDecoder
41
+ def decode(data)
42
+ begin
43
+ r = Net::DNS::Packet.parse(data)
44
+ return if not r
45
+
46
+ # XXX: This can throw an exception on bad data
47
+ vers = r.answer.map{|x| x.txt.strip rescue nil }.reject{|x| x.nil? }.first
48
+ return if not vers
49
+ return { "dns_version" => vers }
50
+ rescue ::Exception
51
+ { }
52
+ end
53
+ end
54
+ end
55
+
56
+ #
57
+ # Decode a SSDP probe response ( zmap: upnp_1900.pkt )
58
+ #
59
+ class FilterDecodeUPNP_SSDP_Reply
60
+ include BaseDecoder
61
+ def decode(data)
62
+ head = { }
63
+ data.split(/\n/).each do |line|
64
+ k,v = line.strip.split(':', 2)
65
+ next if not k
66
+ head["upnp_#{k.downcase}"] = (v.to_s.strip)
67
+ end
68
+ head
69
+ end
70
+ end
71
+
72
+ #
73
+ # Decode a VxWorks WDBRPC probe response ( zmap: wdbrpc_17185.pkt )
74
+ #
75
+ class FilterDecodeWDBRPC_Reply
76
+ include BaseDecoder
77
+ def decode(data)
78
+ info = {}
79
+ head = buff.slice!(0,36)
80
+ info['agent_ver'] = wdbrpc_decode_str(buff)
81
+ info['agent_mtu'] = wdbrpc_decode_int(buff)
82
+ info['agent_mod'] = wdbrpc_decode_int(buff)
83
+ info['rt_type'] = wdbrpc_decode_int(buff)
84
+ info['rt_vers'] = wdbrpc_decode_str(buff)
85
+ info['rt_cpu_type'] = wdbrpc_decode_int(buff)
86
+ info['rt_has_fpp'] = wdbrpc_decode_bool(buff)
87
+ info['rt_has_wp'] = wdbrpc_decode_bool(buff)
88
+ info['rt_page_size'] = wdbrpc_decode_int(buff)
89
+ info['rt_endian'] = wdbrpc_decode_int(buff)
90
+ info['rt_bsp_name'] = wdbrpc_decode_str(buff)
91
+ info['rt_bootline'] = wdbrpc_decode_str(buff)
92
+ info['rt_membase'] = wdbrpc_decode_int(buff)
93
+ info['rt_memsize'] = wdbrpc_decode_int(buff)
94
+ info['rt_region_count'] = wdbrpc_decode_int(buff)
95
+ info['rt_regions'] = wdbrpc_decode_arr(buff, :int)
96
+ info['rt_hostpool_base'] = wdbrpc_decode_int(buff)
97
+ info['rt_hostpool_size'] = wdbrpc_decode_int(buff)
98
+ info
99
+ end
100
+ end
101
+
102
+ #
103
+ # Decode a SNMP GET probe response ( zmap: snmp1_161.pkt )
104
+ #
105
+ class FilterDecodeSNMPGetReply
106
+ include BaseDecoder
107
+ def decode(data)
108
+ asn = OpenSSL::ASN1.decode(data) rescue nil
109
+ return if not asn
110
+
111
+ snmp_error = asn.value[0].value rescue nil
112
+ snmp_comm = asn.value[1].value rescue nil
113
+ snmp_data = asn.value[2].value[3].value[0] rescue nil
114
+ snmp_oid = snmp_data.value[0].value rescue nil
115
+ snmp_info = snmp_data.value[1].value rescue nil
116
+
117
+ return if not (snmp_error and snmp_comm and snmp_data and snmp_oid and snmp_info)
118
+ snmp_info = snmp_info.to_s.gsub(/\s+/, ' ').gsub(/[\x00-\x1f]/, ' ')
119
+
120
+ return if not snmp_info
121
+ { 'snmp_value' => snmp_info }
122
+ end
123
+ end
124
+
125
+ #
126
+ # Decode a IPMI GetChannelAuth probe response ( zmap: ipmi_623.pkt )
127
+ #
128
+ class FilterDecodeIPMIChanAuthReply
129
+ include BaseDecoder
130
+ def decode(data)
131
+ info = Dap::Proto::IPMI::Channel_Auth_Reply.new(data)
132
+ return unless info.valid?
133
+ {}.tap do |h|
134
+ info.fields.each do |f|
135
+ name = f.name
136
+ h[name] = info.send(name).to_s
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ #
143
+ # Decode a NAT-PMP External Address response
144
+ #
145
+ class FilterDecodeNATPMPExternalAddressResponse
146
+ include BaseDecoder
147
+ def decode(data)
148
+ return unless (data && data.size == Dap::Proto::NATPMP::REQUIRED_SIZE)
149
+ info = Dap::Proto::NATPMP::ExternalAddressResponse.new(data)
150
+ {}.tap do |h|
151
+ info.fields.each do |f|
152
+ name = f.name
153
+ h[name] = info.send(name).to_s
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ #
160
+ # Decode a NetBIOS status probe response ( zmap: netbios_137.pkt )
161
+ #
162
+ class FilterDecodeNetbiosStatusReply
163
+ include BaseDecoder
164
+ def decode(data)
165
+ ret = {}
166
+ head = data.slice!(0,12)
167
+
168
+ xid, flags, quests, answers, auths, adds = head.unpack('n6')
169
+ return if quests != 0
170
+ return if answers == 0
171
+
172
+ qname = data.slice!(0,34)
173
+ rtype,rclass,rttl,rlen = data.slice!(0,10).unpack('nnNn')
174
+ return if not rlen
175
+
176
+ buff = data.slice!(0,rlen)
177
+
178
+ names = []
179
+
180
+ case rtype
181
+ when 0x21
182
+ hname = nil
183
+ inf = ''
184
+ rcnt = buff.slice!(0,1).unpack("C")[0]
185
+ return unless rcnt
186
+ 1.upto(rcnt) do
187
+ tname = buff.slice!(0,15).gsub(/\x00.*/, '').strip
188
+ ttype = buff.slice!(0,1).unpack("C")[0]
189
+ tflag = buff.slice!(0,2).unpack('n')[0]
190
+ names << [ tname, ttype, tflag ]
191
+ end
192
+
193
+ maddr = buff.slice!(0,6).unpack("C*").map{|c| "%.2x" % c }.join(":")
194
+ names.each do |name|
195
+ inf << name[0]
196
+
197
+ next unless name[1]
198
+ inf << ":%.2x" % name[1]
199
+
200
+ next unless name[2]
201
+ if (name[2] & 0x8000 == 0)
202
+ inf << ":U "
203
+ else
204
+ inf << ":G "
205
+ end
206
+ end
207
+ end
208
+
209
+ return unless names.length > 0
210
+
211
+ {}.tap do |hash|
212
+ hash['netbios_names'] = (inf)
213
+ hash['netbios_mac'] = maddr
214
+ hash['netbios_hname'] = names[0][0]
215
+ unless maddr == '00:00:00:00:00:00'
216
+ hash['netbios_mac_company'] = mac_company(maddr)
217
+ hash['netbios_mac_company_name'] = mac_company_name(maddr)
218
+ end
219
+ end
220
+ end
221
+
222
+ def mac_company(address)
223
+ begin
224
+ name = Dap::Utils::Oui.lookup_oui_fullname(address)
225
+ name.split("/").first.strip
226
+ rescue => error
227
+ ''
228
+ end
229
+ end
230
+
231
+ def mac_company_name(address)
232
+ begin
233
+ Dap::Utils::Oui.lookup_oui_company_name(address)
234
+ rescue => error
235
+ ''
236
+ end
237
+ end
238
+ end
239
+ #
240
+ # Decode a MSSQL reply
241
+ #
242
+ class FilterDecodeMSSQLReply
243
+ include BaseDecoder
244
+ def decode(data)
245
+ info = {}
246
+ # Some binary characters often proceed key, restrict to alphanumeric and a few other common chars
247
+ data.scan(/([A-Za-z0-9 \.\-_]+?);(.+?);/).each do | var, val|
248
+ info["mssql.#{var.encode!( 'UTF-8', invalid: :replace, undef: :replace, replace: '' )}"] = val.encode!( 'UTF-8', invalid: :replace, undef: :replace, replace: '' )
249
+ end
250
+ info
251
+ end
252
+ end
253
+
254
+ #
255
+ # Decode a SIP OPTIONS Reply
256
+ #
257
+ class FilterDecodeSIPOptionsReply
258
+ include BaseDecoder
259
+ def decode(data)
260
+ info = {}
261
+
262
+ return info unless (data and data.length > 0)
263
+
264
+ head,body = data.to_s.split(/\r?\n\r?\n/, 2)
265
+
266
+ head.split(/\r?\n/).each do |line|
267
+ case line
268
+ when /^SIP\/(\d+\.\d+) (\d+)(.*)/
269
+ info['sip_version'] = $1
270
+ info['sip_code'] = $2
271
+ if $3.length > 0
272
+ info['sip_message'] = $3.strip
273
+ end
274
+ when /^([a-zA-z0-9][^:]+):(.*)/
275
+ var = $1.strip
276
+ val = $2.strip
277
+ var = var.downcase.gsub(/[^a-zA-Z0-9_]/, '_').gsub(/_+/, '_')
278
+ info["sip_#{var}"] = val
279
+ end
280
+ end
281
+
282
+ if body and body.length > 0
283
+ info['sip_data'] = body
284
+ end
285
+
286
+ info
287
+ end
288
+ end
289
+
290
+ #
291
+ # Quickly decode a DTLS message
292
+ #
293
+ class FilterDecodeDTLS
294
+ include BaseDecoder
295
+ def decode(data)
296
+ return unless data.length >= 13
297
+ info = Dap::Proto::DTLS::RecordLayer.new(data)
298
+ return unless (info && info.valid?)
299
+ {}.tap do |h|
300
+ info.fields.each do |f|
301
+ name = f.name
302
+ h[name] = info.send(name).to_s
303
+ end
304
+ end
305
+ end
306
+ end
307
+
308
+ #
309
+ # Decode a NTP reply
310
+ #
311
+ class FilterDecodeNTPReply
312
+ include BaseDecoder
313
+ def decode(sdata)
314
+ info = {}
315
+ return if sdata.length < 4
316
+
317
+ # Make a copy since our parser is destructive
318
+ data = sdata.dup
319
+
320
+ # TODO: all of this with bitstruct?
321
+ # The format of the packet depends largely on the version, so extract just the version.
322
+ # Fortunately the version is in the same place regardless of NTP protocol version --
323
+ # The 3rd-5th bits of the first byte of the response
324
+ ntp_flags = data.slice!(0,1).unpack('C').first
325
+ ntp_version = (ntp_flags & 0b00111000) >> 3
326
+ info['ntp.version'] = ntp_version
327
+
328
+ # NTP 2 & 3 share a common header, so parse those together
329
+ if ntp_version == 2 || ntp_version == 3
330
+ # 0 1 2 3
331
+ # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
332
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
333
+ # |R|M| VN | Mode|A| Sequence | Implementation| Req Code |
334
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
335
+
336
+ info['ntp.response'] = ntp_flags >> 7
337
+ info['ntp.more'] = (ntp_flags & 0b01000000) >> 6
338
+ info['ntp.mode'] = (ntp_flags & 0b00000111)
339
+ ntp_auth_seq, ntp_impl, ntp_rcode = data.slice!(0,3).unpack('C*')
340
+ info['ntp.implementation'] = ntp_impl
341
+ info['ntp.request_code'] = ntp_rcode
342
+
343
+ # if it is mode 7, parse that:
344
+ # 0 1 2 3
345
+ # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
346
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
347
+ # | Err | Number of data items | MBZ | Size of data item |
348
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
349
+ # ... data ...
350
+ if info['ntp.mode'] == 7
351
+ return info if data.size < 4
352
+ mode7_data = data.slice!(0,4).unpack('n*')
353
+ info['ntp.mode7.err'] = mode7_data.first >> 11
354
+ info['ntp.mode7.data_items_count'] = mode7_data.first & 0b0000111111111111
355
+ info['ntp.mode7.mbz'] = mode7_data.last >> 11
356
+ info['ntp.mode7.data_item_size'] = mode7_data.last & 0b0000111111111111
357
+
358
+ # extra monlist response data
359
+ if ntp_rcode == 42
360
+ if info['ntp.mode7.data_item_size'] == 72
361
+ remote_addresses = []
362
+ local_addresses = []
363
+ idx = 0
364
+ 1.upto(info['ntp.mode7.data_items_count']) do
365
+
366
+ #u_int32 firsttime; /* first time we received a packet */
367
+ #u_int32 lasttime; /* last packet from this host */
368
+ #u_int32 restr; /* restrict bits (was named lastdrop) */
369
+ #u_int32 count; /* count of packets received */
370
+ #u_int32 addr; /* host address V4 style */
371
+ #u_int32 daddr; /* destination host address */
372
+ #u_int32 flags; /* flags about destination */
373
+ #u_short port; /* port number of last reception */
374
+
375
+ firsttime,lasttime,restr,count,raddr,laddr,flags,dport = data[idx, 30].unpack("NNNNNNNn")
376
+ remote_addresses << [raddr].pack("N").unpack("C*").map{|x| x.to_s }.join(".")
377
+ local_addresses << [laddr].pack("N").unpack("C*").map{|x| x.to_s }.join(".")
378
+ idx += info['ntp.mode7.data_item_size']
379
+ end
380
+
381
+ info['ntp.monlist.remote_addresses'] = remote_addresses.join(' ')
382
+ info['ntp.monlist.remote_addresses.count'] = remote_addresses.size
383
+ info['ntp.monlist.local_addresses'] = local_addresses.join(' ')
384
+ info['ntp.monlist.local_addresses.count'] = local_addresses.size
385
+ end
386
+ end
387
+ end
388
+ elsif ntp_version == 4
389
+ info['ntp.leap_indicator'] = ntp_flags >> 6
390
+ info['ntp.mode'] = ntp_flags & 0b00000111
391
+ info['ntp.peer.stratum'], info['ntp.peer.interval'], info['ntp.peer.precision'] = data.slice!(0,3).unpack('C*')
392
+ info['ntp.root.delay'], info['ntp.root.dispersion'], info['ntp.ref_id'] = data.slice!(0,12).unpack('N*')
393
+ info['ntp.timestamp.reference'], info['ntp.timestamp.origin'], info['ntp.timestamp.receive'], info['ntp.timestamp.transmit'] = data.slice!(0,32).unpack('Q*')
394
+ end
395
+
396
+ info
397
+ end
398
+ end
399
+
400
+ end
401
+ end