pcapr-local 0.1.10
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.
- data/.document +5 -0
- data/LICENSE.txt +20 -0
- data/README.md +64 -0
- data/Rakefile +57 -0
- data/VERSION +1 -0
- data/bin/pcap2par +49 -0
- data/bin/startpcapr +40 -0
- data/bin/stoppcapr +33 -0
- data/bin/xtractr +5 -0
- data/lib/environment.rb +106 -0
- data/lib/exe/xtractr +0 -0
- data/lib/mu/pcap.rb +110 -0
- data/lib/mu/pcap/ethernet.rb +148 -0
- data/lib/mu/pcap/header.rb +75 -0
- data/lib/mu/pcap/io_pair.rb +67 -0
- data/lib/mu/pcap/io_wrapper.rb +76 -0
- data/lib/mu/pcap/ip.rb +61 -0
- data/lib/mu/pcap/ipv4.rb +257 -0
- data/lib/mu/pcap/ipv6.rb +148 -0
- data/lib/mu/pcap/packet.rb +104 -0
- data/lib/mu/pcap/pkthdr.rb +155 -0
- data/lib/mu/pcap/reader.rb +61 -0
- data/lib/mu/pcap/reader/http_family.rb +170 -0
- data/lib/mu/pcap/sctp.rb +367 -0
- data/lib/mu/pcap/sctp/chunk.rb +123 -0
- data/lib/mu/pcap/sctp/chunk/data.rb +134 -0
- data/lib/mu/pcap/sctp/chunk/init.rb +100 -0
- data/lib/mu/pcap/sctp/chunk/init_ack.rb +68 -0
- data/lib/mu/pcap/sctp/parameter.rb +110 -0
- data/lib/mu/pcap/sctp/parameter/ip_address.rb +48 -0
- data/lib/mu/pcap/stream_packetizer.rb +72 -0
- data/lib/mu/pcap/tcp.rb +505 -0
- data/lib/mu/pcap/udp.rb +69 -0
- data/lib/mu/scenario/pcap.rb +164 -0
- data/lib/mu/scenario/pcap/fields.rb +50 -0
- data/lib/mu/scenario/pcap/rtp.rb +71 -0
- data/lib/pcapr_local.rb +159 -0
- data/lib/pcapr_local/config.rb +336 -0
- data/lib/pcapr_local/db.rb +197 -0
- data/lib/pcapr_local/scanner.rb +250 -0
- data/lib/pcapr_local/server.rb +178 -0
- data/lib/pcapr_local/www/favicon.ico +0 -0
- data/lib/pcapr_local/www/favicon.png +0 -0
- data/lib/pcapr_local/www/home/index.html +138 -0
- data/lib/pcapr_local/www/static/image/16x16/Cancel.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Cancel.png.1 +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Download.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Folder3.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Full Size.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Minus.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Plus.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Search.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/User.png +0 -0
- data/lib/pcapr_local/www/static/image/48x48/Phone.png +0 -0
- data/lib/pcapr_local/www/static/image/48x48/Video.png +0 -0
- data/lib/pcapr_local/www/static/image/bar-orange.gif +0 -0
- data/lib/pcapr_local/www/static/image/beta.png +0 -0
- data/lib/pcapr_local/www/static/image/bg.png +0 -0
- data/lib/pcapr_local/www/static/image/blockquote.png +0 -0
- data/lib/pcapr_local/www/static/image/body-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl1-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl1-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl1-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl2-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl2-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl2-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl3-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl3-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl3-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl4-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl4-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl4-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl5-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl6-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl7-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl8-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/bottom-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/bottom-l.png +0 -0
- data/lib/pcapr_local/www/static/image/bottom-r.png +0 -0
- data/lib/pcapr_local/www/static/image/btn-search.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-1.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-2.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-3.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-4.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-5.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-6.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-7.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-hl1.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-hl2.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-hl3.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-hl4.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-pathway.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-section1.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-section2.png +0 -0
- data/lib/pcapr_local/www/static/image/collapsed.gif +0 -0
- data/lib/pcapr_local/www/static/image/crosslink.png +0 -0
- data/lib/pcapr_local/www/static/image/expanded.gif +0 -0
- data/lib/pcapr_local/www/static/image/favicon.ico +0 -0
- data/lib/pcapr_local/www/static/image/favicon.png +0 -0
- data/lib/pcapr_local/www/static/image/icon-author.png +0 -0
- data/lib/pcapr_local/www/static/image/icon-created.png +0 -0
- data/lib/pcapr_local/www/static/image/p-expand.gif +0 -0
- data/lib/pcapr_local/www/static/image/pcapr-logo.png +0 -0
- data/lib/pcapr_local/www/static/image/powered-by.png +0 -0
- data/lib/pcapr_local/www/static/image/section1-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/section1-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/section1-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/section2-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/section2-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/section2-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/status-alert.png +0 -0
- data/lib/pcapr_local/www/static/image/status-download.png +0 -0
- data/lib/pcapr_local/www/static/image/status-info.png +0 -0
- data/lib/pcapr_local/www/static/image/status-note.png +0 -0
- data/lib/pcapr_local/www/static/image/tab-round.png +0 -0
- data/lib/pcapr_local/www/static/image/throbber.gif +0 -0
- data/lib/pcapr_local/www/static/image/user.jpg +0 -0
- data/lib/pcapr_local/www/static/script/closet/async.js +421 -0
- data/lib/pcapr_local/www/static/script/closet/closet.api.js +241 -0
- data/lib/pcapr_local/www/static/script/closet/closet.folders.js +94 -0
- data/lib/pcapr_local/www/static/script/closet/closet.js +187 -0
- data/lib/pcapr_local/www/static/script/closet/closet.mr.js +219 -0
- data/lib/pcapr_local/www/static/script/closet/closet.options.js +359 -0
- data/lib/pcapr_local/www/static/script/closet/closet.quantity.js +73 -0
- data/lib/pcapr_local/www/static/script/closet/closet.render.js +205 -0
- data/lib/pcapr_local/www/static/script/closet/closet.report.js +86 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.http.js +135 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.overview.js +163 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.sip.js +159 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.tcp.js +72 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.visualize.js +263 -0
- data/lib/pcapr_local/www/static/script/closet/closet.util.js +40 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery-1.4.2.min.js +154 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery-ui.js +10921 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.flot.js +2123 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.flot.selection.js +184 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.flot.stack.js +184 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.form.js +643 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.jsonp.min.js +3 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.menu.js +142 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.suggest.js +308 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.ui.core.js +203 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.ui.slider.js +629 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.ui.sortable.js +1055 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.ui.widget.js +236 -0
- data/lib/pcapr_local/www/static/script/json2.js +481 -0
- data/lib/pcapr_local/www/static/script/sammy/plugins/sammy.cache.js +115 -0
- data/lib/pcapr_local/www/static/script/sammy/plugins/sammy.template.js +117 -0
- data/lib/pcapr_local/www/static/script/sammy/sammy.js +1696 -0
- data/lib/pcapr_local/www/static/script/tipsy/jquery.tipsy.js +104 -0
- data/lib/pcapr_local/www/static/style/c3p0.css +116 -0
- data/lib/pcapr_local/www/static/style/jquery.suggest.css +27 -0
- data/lib/pcapr_local/www/static/style/page.css +1113 -0
- data/lib/pcapr_local/www/static/style/tipsy.css +7 -0
- data/lib/pcapr_local/www/templates/browse.services.template +10 -0
- data/lib/pcapr_local/www/templates/browse.template +77 -0
- data/lib/pcapr_local/www/templates/flows.template +38 -0
- data/lib/pcapr_local/www/templates/pcap.template +63 -0
- data/lib/pcapr_local/www/templates/sip.calls.template +35 -0
- data/lib/pcapr_local/www/templates/statistics.template +6 -0
- data/lib/pcapr_local/xtractr.rb +179 -0
- data/lib/pcapr_local/xtractr/instance.rb +172 -0
- data/pcapr-local.gemspec +297 -0
- data/test/mu/pcap/reader/tc_http_family.rb +251 -0
- data/test/mu/pcap/tc_ethernet.rb +71 -0
- data/test/mu/pcap/tc_header.rb +56 -0
- data/test/mu/pcap/tc_ipv4.rb +103 -0
- data/test/mu/pcap/tc_ipv6.rb +83 -0
- data/test/mu/pcap/tc_packet.rb +44 -0
- data/test/mu/pcap/tc_pair.rb +58 -0
- data/test/mu/pcap/tc_pkthdr.rb +33 -0
- data/test/mu/pcap/tc_reader.rb +76 -0
- data/test/mu/pcap/tc_tcp.rb +426 -0
- data/test/mu/pcap/tc_udp.rb +33 -0
- data/test/mu/pcap/tc_wrapper.rb +80 -0
- data/test/mu/scenario/pcap/tc_fields.rb +67 -0
- data/test/mu/scenario/pcap/tc_rtp.rb +135 -0
- data/test/mu/scenario/sip_signalled_call_1.pcap +0 -0
- data/test/mu/scenario/tc_pcap.rb +190 -0
- data/test/mu/scenario/test_data/arp.pcap +0 -0
- data/test/mu/scenario/test_data/dns.pcap +0 -0
- data/test/mu/scenario/test_data/http-v6.pcap +0 -0
- data/test/mu/scenario/test_data/http.pcap +0 -0
- data/test/mu/scenario/test_data/http_chunked.pcap +0 -0
- data/test/mu/scenario/test_data/http_deflate.pcap +0 -0
- data/test/mu/scenario/test_data/httpauth3.pcap +0 -0
- data/test/mu/scenario/test_data/icmp.pcap +0 -0
- data/test/mu/scenario/test_data/sip_signalled_call_1.pcap +0 -0
- data/test/mu/tc_pcap.rb +39 -0
- data/test/mu/testcase.rb +86 -0
- data/test/pcapr_local/arp.pcap +0 -0
- data/test/pcapr_local/data.js +3 -0
- data/test/pcapr_local/http_chunked.pcap +0 -0
- data/test/pcapr_local/tc_api.rb +181 -0
- data/test/pcapr_local/test.tgz +0 -0
- data/test/pcapr_local/test_scanner.rb +241 -0
- data/test/pcapr_local/test_xtractr.rb +219 -0
- data/test/pcapr_local/testcase.rb +107 -0
- data/test/test_export_to_scenario.sh +25 -0
- data/test/test_pcapr_local.rb +29 -0
- metadata +450 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# http://www.mudynamics.com
|
|
2
|
+
# http://labs.mudynamics.com
|
|
3
|
+
# http://www.pcapr.net
|
|
4
|
+
|
|
5
|
+
require 'find'
|
|
6
|
+
require 'set'
|
|
7
|
+
require 'fileutils'
|
|
8
|
+
require 'tempfile'
|
|
9
|
+
require 'digest/md5'
|
|
10
|
+
|
|
11
|
+
module PcaprLocal
|
|
12
|
+
class Scanner
|
|
13
|
+
|
|
14
|
+
# Creates scanner instance and starts it.
|
|
15
|
+
def self.start config
|
|
16
|
+
scanner = Scanner.new config
|
|
17
|
+
scanner.start
|
|
18
|
+
scanner
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Runs scanner loop in separate thread.
|
|
22
|
+
def start
|
|
23
|
+
Logger.info "Starting scanner thread"
|
|
24
|
+
Thread.new do
|
|
25
|
+
loop do
|
|
26
|
+
begin
|
|
27
|
+
scan
|
|
28
|
+
@db.compact!
|
|
29
|
+
rescue Exception => e
|
|
30
|
+
Logger.error "Exception during scanning: #{e.message}\n" + e.backtrace.join("\n")
|
|
31
|
+
end
|
|
32
|
+
sleep @scan_interval
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def initialize config
|
|
38
|
+
@db = config.fetch('db')
|
|
39
|
+
@xtractr = config.fetch('xtractr')
|
|
40
|
+
@pcap_dir = File.expand_path(config.fetch('pcap_dir'))
|
|
41
|
+
@index_dir = File.expand_path(config.fetch('index_dir'))
|
|
42
|
+
@queue_delay = config.fetch('queue_delay')
|
|
43
|
+
@scan_interval = config.fetch('interval')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Removes doc from database and corresponding index file.
|
|
47
|
+
# Does _not_ remove original pcap.
|
|
48
|
+
def remove_doc doc
|
|
49
|
+
@db.delete_doc(doc)
|
|
50
|
+
if filename = doc['filename']
|
|
51
|
+
FileUtils.rm_f pcap_path(filename)
|
|
52
|
+
remove_index_for(filename)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def scan
|
|
57
|
+
# Get list of all pcaps
|
|
58
|
+
pcaps = self.find_pcaps
|
|
59
|
+
# Cleanup db and queue new pcaps
|
|
60
|
+
reconcile_with_db pcaps
|
|
61
|
+
# Index queued pcaps.
|
|
62
|
+
self.index
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
RE_PCAP = /\.p?cap\Z/
|
|
66
|
+
|
|
67
|
+
# Returns a set of pcap files (relative paths)
|
|
68
|
+
def find_pcaps
|
|
69
|
+
if not File.directory?(@pcap_dir) or not File.readable?(@pcap_dir)
|
|
70
|
+
return Set.new
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
pcaps = Set.new
|
|
74
|
+
pcap_prefix_size = @pcap_dir.size + 1 # /
|
|
75
|
+
Find.find @pcap_dir do |path|
|
|
76
|
+
# Don't recurse into ".pcapr_local" or other "." dirs
|
|
77
|
+
if File.basename(path) =~ /^\./
|
|
78
|
+
Find.prune
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Should be a file ending in .cap or .pcap
|
|
82
|
+
next unless path =~ RE_PCAP and File.file?(path)
|
|
83
|
+
|
|
84
|
+
rel_path = path[pcap_prefix_size..-1]
|
|
85
|
+
pcaps << rel_path
|
|
86
|
+
end
|
|
87
|
+
pcaps
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def requeue_pcap rel_path
|
|
91
|
+
res = @db.view("pcaps/by_filename", :key => rel_path)
|
|
92
|
+
return nil if res['rows'].empty?
|
|
93
|
+
|
|
94
|
+
id = res['rows'][0]["id"]
|
|
95
|
+
@db.update_doc id do |doc|
|
|
96
|
+
doc['status'] = 'queued'
|
|
97
|
+
doc.delete 'index'
|
|
98
|
+
doc
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Adds pcap to db with status set to "queued". Returns nil w/out
|
|
103
|
+
# updating db if the pcap was modified within the last queue_delay
|
|
104
|
+
# seconds (because pcap may not be completely copied to pcap_dir).
|
|
105
|
+
def add_pcap relative_path
|
|
106
|
+
now = Time.new
|
|
107
|
+
stat = File.stat(File.join(@pcap_dir, relative_path))
|
|
108
|
+
if now - stat.mtime < @queue_delay
|
|
109
|
+
return
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Pick determistic doc id based on path and pcap size.
|
|
113
|
+
# (for testing convenience).
|
|
114
|
+
id = Digest::MD5.new
|
|
115
|
+
id << "#{relative_path}:#{stat.size}"
|
|
116
|
+
|
|
117
|
+
doc = CouchRest::Document.new({
|
|
118
|
+
:_id => id.to_s,
|
|
119
|
+
:type => 'pcap',
|
|
120
|
+
:filename => relative_path,
|
|
121
|
+
:status => 'queued',
|
|
122
|
+
:stat => {
|
|
123
|
+
:inode => stat.ino,
|
|
124
|
+
:size => stat.size,
|
|
125
|
+
:ctime => stat.ctime,
|
|
126
|
+
},
|
|
127
|
+
:created_at => now,
|
|
128
|
+
:updated_at => now,
|
|
129
|
+
})
|
|
130
|
+
@db.save_doc doc
|
|
131
|
+
|
|
132
|
+
doc
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Indexes all documents in queue. Returns count of documents indexed.
|
|
136
|
+
def index
|
|
137
|
+
count = 0
|
|
138
|
+
@db.each_in_view("pcaps/queued", :include_docs => true) do |row|
|
|
139
|
+
index_pcap row['doc']
|
|
140
|
+
count += 1
|
|
141
|
+
end
|
|
142
|
+
count
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Creates xtractr index for pcap. Updates status from "queued" to "indexing" to "indexed".
|
|
146
|
+
# Any exception will result in a status of "failed" with the exception's message copied
|
|
147
|
+
# to the document's message attribute.
|
|
148
|
+
def index_pcap pcap
|
|
149
|
+
relative_path = pcap["filename"]
|
|
150
|
+
pcap_path = File.join(File.expand_path(@pcap_dir), relative_path)
|
|
151
|
+
index_dir = File.join(File.expand_path(@index_dir), relative_path)
|
|
152
|
+
|
|
153
|
+
# Index
|
|
154
|
+
Logger.info "Indexing #{relative_path}"
|
|
155
|
+
begin
|
|
156
|
+
@db.update_doc pcap["_id"] do |doc|
|
|
157
|
+
doc["status"] = "indexing"
|
|
158
|
+
doc
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
index_data = @xtractr.index pcap_path, index_dir
|
|
162
|
+
|
|
163
|
+
@db.update_doc pcap["_id"] do |doc|
|
|
164
|
+
doc['index'] = index_data
|
|
165
|
+
doc['status'] = 'indexed'
|
|
166
|
+
doc
|
|
167
|
+
end
|
|
168
|
+
rescue
|
|
169
|
+
Logger.warn "Indexing failure: #{$!.message}"
|
|
170
|
+
@db.update_doc pcap["_id"] do |doc|
|
|
171
|
+
doc['status'] = "failed"
|
|
172
|
+
doc['message'] = $!.message
|
|
173
|
+
doc
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
return
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def pcap_path rel_path
|
|
181
|
+
if rel_path.is_a? Hash
|
|
182
|
+
rel_path = rel_path[:filename] or raise "path not found in #{rel_path.inspect}"
|
|
183
|
+
end
|
|
184
|
+
File.expand_path File.join(@pcap_dir, rel_path)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def index_path rel_path
|
|
188
|
+
if rel_path.is_a? Hash
|
|
189
|
+
rel_path = rel_path.fetch :filename
|
|
190
|
+
end
|
|
191
|
+
File.expand_path File.join(@index_dir, rel_path)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Because FileUtils.rm_rf is too dangerous.
|
|
195
|
+
def remove_index_for rel_path
|
|
196
|
+
target = index_path rel_path
|
|
197
|
+
if File.directory? target
|
|
198
|
+
FileUtils.rm_rf Dir.glob("#{target}/*.db")
|
|
199
|
+
FileUtils.rmdir target rescue nil
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Checks each pcap in the database, purging or requeueing documents as necessary.
|
|
204
|
+
# Any pcaps in fs_pcaps that are not in the database are added.
|
|
205
|
+
def reconcile_with_db fs_pcaps
|
|
206
|
+
fs_pcaps = fs_pcaps.dup
|
|
207
|
+
|
|
208
|
+
indexed = Set.new
|
|
209
|
+
@db.each_in_view("pcaps/indexed") do |row|
|
|
210
|
+
indexed << row['key']
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
@db.each_in_view("pcaps/by_filename") do |row|
|
|
214
|
+
path = row['key']
|
|
215
|
+
|
|
216
|
+
# Delete record if from database if pcap is not present on the
|
|
217
|
+
# file system.
|
|
218
|
+
if not fs_pcaps.include? path
|
|
219
|
+
Logger.warn "Indexer: removing database entry for missing pcap #{path}"
|
|
220
|
+
@db.delete_doc @db.get(row['id'])
|
|
221
|
+
remove_index_for(path)
|
|
222
|
+
|
|
223
|
+
next
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Requeue pcap if xtractr index is missing or is older than the pcap.
|
|
227
|
+
if indexed.include? path
|
|
228
|
+
pcap_index_dir = File.join(@index_dir, path)
|
|
229
|
+
if not Xtractr.index_dir?(pcap_index_dir)
|
|
230
|
+
Logger.warn "Index is missing, requeueing #{path}"
|
|
231
|
+
requeue_pcap path
|
|
232
|
+
elsif Xtractr.index_time(pcap_index_dir) < File.mtime(pcap_path(path)).to_f
|
|
233
|
+
Logger.info "Pcap is newer than index, requeueing #{path}"
|
|
234
|
+
requeue_pcap path
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
fs_pcaps.delete path
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Remaining pcaps are unknown, add them to database
|
|
242
|
+
fs_pcaps.each do |path|
|
|
243
|
+
Logger.debug "New pcap: #{path}"
|
|
244
|
+
add_pcap path
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# http://www.mudynamics.com
|
|
2
|
+
# http://labs.mudynamics.com
|
|
3
|
+
# http://www.pcapr.net
|
|
4
|
+
|
|
5
|
+
require 'rack'
|
|
6
|
+
require 'rack/contrib/jsonp'
|
|
7
|
+
require 'sinatra'
|
|
8
|
+
require 'pcapr_local/db'
|
|
9
|
+
require 'pcapr_local/scanner'
|
|
10
|
+
require 'pcapr_local/xtractr'
|
|
11
|
+
require 'mu/scenario/pcap'
|
|
12
|
+
|
|
13
|
+
module PcaprLocal
|
|
14
|
+
class Server < Sinatra::Base
|
|
15
|
+
set :app_file, __FILE__
|
|
16
|
+
root = File.expand_path(File.dirname(__FILE__))
|
|
17
|
+
set :public, File.join(root, 'www')
|
|
18
|
+
|
|
19
|
+
use Rack::JSONP
|
|
20
|
+
mime_type :template, 'application/octet-stream'
|
|
21
|
+
mime_type :par, 'application/octet-stream'
|
|
22
|
+
|
|
23
|
+
helpers do
|
|
24
|
+
# View as object by joining keys and values.
|
|
25
|
+
# e.g. {"key" => "foo", "value" => "bar"}
|
|
26
|
+
# becomes
|
|
27
|
+
# {"foo" => "bar"}
|
|
28
|
+
def as_object result
|
|
29
|
+
obj = {}
|
|
30
|
+
result['rows'].each do |row|
|
|
31
|
+
key = row['key']
|
|
32
|
+
if key.is_a? Array
|
|
33
|
+
# behave like javascript Array.toString
|
|
34
|
+
key = key.join ","
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
val = row['value']
|
|
38
|
+
if val.is_a? Array
|
|
39
|
+
# behave like javascript Array.toString
|
|
40
|
+
key = key.join ","
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
obj[key] = val
|
|
44
|
+
end
|
|
45
|
+
obj
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Main page.
|
|
50
|
+
get '/' do
|
|
51
|
+
redirect '/home/index.html'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Count of pcaps by status.
|
|
55
|
+
get '/pcaps/1/status' do
|
|
56
|
+
db = settings.db
|
|
57
|
+
content_type :json
|
|
58
|
+
result = db.view 'pcaps/by_status', :group => true
|
|
59
|
+
return as_object(result).to_json
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# High level statistics.
|
|
63
|
+
get '/pcaps/1/statistics' do
|
|
64
|
+
db = settings.db
|
|
65
|
+
content_type :json
|
|
66
|
+
|
|
67
|
+
result = db.view 'pcaps/statistics', :group => true
|
|
68
|
+
|
|
69
|
+
as_object(result).to_json
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
VIEWS = {
|
|
73
|
+
'date' => 'pcaps/by_created_at',
|
|
74
|
+
'path' => 'pcaps/by_path',
|
|
75
|
+
'status' => 'pcaps/by_status',
|
|
76
|
+
'service' => 'pcaps/by_service',
|
|
77
|
+
'keyword' => 'pcaps/by_keyword',
|
|
78
|
+
'filename' => 'pcaps/by_filename',
|
|
79
|
+
'directory' => 'pcaps/by_directory',
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# List/query pcaps by CouchDB view.
|
|
83
|
+
get '/pcaps/1/list' do
|
|
84
|
+
content_type :json
|
|
85
|
+
db = settings.db
|
|
86
|
+
query = params.dup
|
|
87
|
+
by = query.delete('by')
|
|
88
|
+
by ||= 'date'
|
|
89
|
+
view = VIEWS[by]
|
|
90
|
+
query.delete 'callback'
|
|
91
|
+
['startkey', 'endkey', 'key'].each do |key|
|
|
92
|
+
if val = query[key]
|
|
93
|
+
# JSON parser doesn't work unless value is an array or
|
|
94
|
+
# hash. (I.e. this fails: JSON.parse(1.to_json)
|
|
95
|
+
# So enclosing the value in a top level array.
|
|
96
|
+
parsed = JSON.parse("[#{val}]").pop
|
|
97
|
+
query[key] = parsed
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
db.view(view, query).to_json
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Returns doc for pcap.
|
|
105
|
+
get '/pcaps/1/about/:id' do
|
|
106
|
+
content_type :json
|
|
107
|
+
settings.db.get(params[:id]).to_json
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Deletes document if it has a failed status.
|
|
111
|
+
get '/pcaps/1/remove/:id' do
|
|
112
|
+
content_type :json
|
|
113
|
+
id = params[:id]
|
|
114
|
+
doc = settings.db.get(params[:id])
|
|
115
|
+
if doc and doc['status'] == 'failed'
|
|
116
|
+
settings.scanner.remove_doc doc
|
|
117
|
+
{'ok' => true}.to_json
|
|
118
|
+
else
|
|
119
|
+
{'error' => true, 'reason' => 'status is not failed'}.to_json
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Explore pcap on pcapr.
|
|
124
|
+
get '/pcaps/1/pcap/:id' do
|
|
125
|
+
id = params[:id]
|
|
126
|
+
doc = settings.db.get(params[:id])
|
|
127
|
+
if doc and doc['index']
|
|
128
|
+
version = doc['index']['about']['version']
|
|
129
|
+
location = "http://www.pcapr.net/xtractr/explore?version=#{version}&" \
|
|
130
|
+
"url=http://#{request.host_with_port}#{request.path}"
|
|
131
|
+
redirect location
|
|
132
|
+
else
|
|
133
|
+
return {"error" => true , "message" => "not found"}.to_json
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Forward requests (GET) to xtractr browser instance.
|
|
138
|
+
get '/pcaps/1/pcap/:id/*' do
|
|
139
|
+
id = params[:id]
|
|
140
|
+
path = params[:splat][0]
|
|
141
|
+
url = "#{path}?#{request.query_string}"
|
|
142
|
+
doc = settings.db.get(params[:id])
|
|
143
|
+
status, headers, body = settings.xtractr.get(doc['filename'], url)
|
|
144
|
+
headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
|
145
|
+
return status, headers, body
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Forward requests (POST) to xtractr browser instance.
|
|
149
|
+
post '/pcaps/1/pcap/:id/*' do
|
|
150
|
+
id = params[:id]
|
|
151
|
+
path = params[:splat][0]
|
|
152
|
+
url = "#{path}?#{request.query_string}"
|
|
153
|
+
doc = settings.db.get(params[:id])
|
|
154
|
+
status, headers, body = settings.xtractr.post(doc['filename'], url, request.body.read)
|
|
155
|
+
headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
|
156
|
+
return status, headers, body
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Download archive that includes dissected and normalized pcap.
|
|
160
|
+
get '/pcaps/1/export_to_par/:id' do
|
|
161
|
+
content_type :par
|
|
162
|
+
id = params[:id]
|
|
163
|
+
doc = settings.db.get(params[:id])
|
|
164
|
+
raise "not found" unless doc
|
|
165
|
+
path = settings.scanner.pcap_path(doc)
|
|
166
|
+
io = Mu::Scenario::Pcap.export_to_par path
|
|
167
|
+
filename = File.basename(doc['filename']).gsub(/[.][^.]*$/, '.par')
|
|
168
|
+
return 200, {'Content-Disposition' => "filename=#{filename}" }, io
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Record any exceptions in main logfile.
|
|
172
|
+
after '/pcaps/*' do
|
|
173
|
+
if err=request.env['sinatra.error']
|
|
174
|
+
PcaprLocal::Logger.error "UI got an error: #{err.message}\n#{err.backtrace.join("\n")}"
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
2
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
3
|
+
<head>
|
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
5
|
+
<link rel="shortcut icon" href="/static/image/favicon.ico"/>
|
|
6
|
+
<title>pcapr.Local - A place to organize your packet captures</title>
|
|
7
|
+
|
|
8
|
+
<link rel="stylesheet" type="text/css" href="/static/style/page.css" media="all"/>
|
|
9
|
+
<link rel="stylesheet" type="text/css" href="/static/style/c3p0.css" media="all"/>
|
|
10
|
+
<link rel="stylesheet" type="text/css" href="/static/style/jquery.suggest.css" media="all"/>
|
|
11
|
+
<link rel="stylesheet" type="text/css" href="/static/style/tipsy.css" media="all"/>
|
|
12
|
+
|
|
13
|
+
<script type="text/javascript" src="/static/script/json2.js">
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<script type="text/javascript" src="/static/script/jquery/jquery-1.4.2.min.js">
|
|
17
|
+
</script>
|
|
18
|
+
<script type="text/javascript" src="/static/script/jquery/jquery-ui.js">
|
|
19
|
+
</script>
|
|
20
|
+
<script type="text/javascript" src="/static/script/jquery/jquery.ui.core.js">
|
|
21
|
+
</script>
|
|
22
|
+
<script type="text/javascript" src="/static/script/jquery/jquery.ui.widget.js">
|
|
23
|
+
</script>
|
|
24
|
+
<script type="text/javascript" src="/static/script/jquery/jquery.ui.sortable.js">
|
|
25
|
+
</script>
|
|
26
|
+
<script type="text/javascript" src="/static/script/jquery/jquery.ui.slider.js">
|
|
27
|
+
</script>
|
|
28
|
+
<script type="text/javascript" src="/static/script/jquery/jquery.form.js">
|
|
29
|
+
</script>
|
|
30
|
+
<script type="text/javascript" src="/static/script/jquery/jquery.menu.js">
|
|
31
|
+
</script>
|
|
32
|
+
<script type="text/javascript" src="/static/script/jquery/jquery.suggest.js">
|
|
33
|
+
</script>
|
|
34
|
+
<script type="text/javascript" src="/static/script/jquery/jquery.flot.js">
|
|
35
|
+
</script>
|
|
36
|
+
<script type="text/javascript" src="/static/script/jquery/jquery.flot.stack.js">
|
|
37
|
+
</script>
|
|
38
|
+
<script type="text/javascript" src="/static/script/jquery/jquery.flot.selection.js">
|
|
39
|
+
</script>
|
|
40
|
+
<script type="text/javascript" src="/static/script/jquery/jquery.jsonp.min.js">
|
|
41
|
+
</script>
|
|
42
|
+
<script type="text/javascript" src="/static/script/tipsy/jquery.tipsy.js">
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<script type="text/javascript" src="/static/script/sammy/sammy.js">
|
|
46
|
+
</script>
|
|
47
|
+
<script type="text/javascript" src="/static/script/sammy/plugins/sammy.template.js">
|
|
48
|
+
</script>
|
|
49
|
+
<script type="text/javascript" src="/static/script/sammy/plugins/sammy.cache.js">
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<script type="text/javascript" src="/static/script/closet/closet.api.js">
|
|
53
|
+
</script>
|
|
54
|
+
<script type="text/javascript" src="/static/script/closet/closet.js">
|
|
55
|
+
</script>
|
|
56
|
+
<script type="text/javascript" src="/static/script/closet/closet.util.js">
|
|
57
|
+
</script>
|
|
58
|
+
<script type="text/javascript" src="/static/script/closet/closet.quantity.js">
|
|
59
|
+
</script>
|
|
60
|
+
<script type="text/javascript" src="/static/script/closet/closet.render.js">
|
|
61
|
+
</script>
|
|
62
|
+
<script type="text/javascript" src="/static/script/closet/closet.folders.js">
|
|
63
|
+
</script>
|
|
64
|
+
<script type="text/javascript" src="/static/script/closet/closet.mr.js">
|
|
65
|
+
</script>
|
|
66
|
+
<script type="text/javascript" src="/static/script/closet/closet.options.js">
|
|
67
|
+
</script>
|
|
68
|
+
<script type="text/javascript" src="/static/script/closet/closet.report.js">
|
|
69
|
+
</script>
|
|
70
|
+
<script type="text/javascript" src="/static/script/closet/closet.reports.visualize.js">
|
|
71
|
+
</script>
|
|
72
|
+
<script type="text/javascript" src="/static/script/closet/closet.reports.overview.js">
|
|
73
|
+
</script>
|
|
74
|
+
<script type="text/javascript" src="/static/script/closet/closet.reports.tcp.js">
|
|
75
|
+
</script>
|
|
76
|
+
<script type="text/javascript" src="/static/script/closet/closet.reports.http.js">
|
|
77
|
+
</script>
|
|
78
|
+
<script type="text/javascript" src="/static/script/closet/closet.reports.sip.js">
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
<script type="text/javascript" src="/static/script/closet/async.js">
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
<script type="text/javascript">
|
|
87
|
+
(function($) {
|
|
88
|
+
$(function() { closet.load(); });
|
|
89
|
+
}(jQuery));
|
|
90
|
+
</script>
|
|
91
|
+
</head>
|
|
92
|
+
<body id="page_bg" class="f-default">
|
|
93
|
+
<div id="container">
|
|
94
|
+
<div id="mainbody">
|
|
95
|
+
<div class="wrapper">
|
|
96
|
+
<div class="content beta">
|
|
97
|
+
<div style="float:left;font-size:large;margin-top:6px;margin-left:5px;padding-right:40px">
|
|
98
|
+
<a style="text-decoration:none;outline:none" href="http://www.mudynamics.com">
|
|
99
|
+
<img class="icon" src="/static/image/favicon.png"/>
|
|
100
|
+
</a>
|
|
101
|
+
<span style="font-weight:bold;letter-spacing:0.1em">pcapr.Local</span>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div class="browse">
|
|
105
|
+
<div class="floater" style="float:right">
|
|
106
|
+
<table style="display:inline">
|
|
107
|
+
<tr>
|
|
108
|
+
<td><img class="icon" src="/static/image/16x16/Search.png"/></td>
|
|
109
|
+
<td><input id="search" type="text"/> </td>
|
|
110
|
+
<td>
|
|
111
|
+
<div class="statistics" style="float:right"></div>
|
|
112
|
+
</td>
|
|
113
|
+
</tr>
|
|
114
|
+
</table>
|
|
115
|
+
</div>
|
|
116
|
+
<ul class="tab">
|
|
117
|
+
<li class="active">
|
|
118
|
+
<a id="browse" href="#/browse"><span>Browse</span></a>
|
|
119
|
+
</li>
|
|
120
|
+
<li>
|
|
121
|
+
<a id="folders" href="#/folders"><span>Folders</span></a>
|
|
122
|
+
</li>
|
|
123
|
+
<li>
|
|
124
|
+
<a id="services" href="#/services"><span>Services</span></a>
|
|
125
|
+
</li>
|
|
126
|
+
</ul>
|
|
127
|
+
<div class="browse-tab">
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div class="footer">
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</body>
|
|
138
|
+
</html>
|