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.
Files changed (203) hide show
  1. data/.document +5 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +64 -0
  4. data/Rakefile +57 -0
  5. data/VERSION +1 -0
  6. data/bin/pcap2par +49 -0
  7. data/bin/startpcapr +40 -0
  8. data/bin/stoppcapr +33 -0
  9. data/bin/xtractr +5 -0
  10. data/lib/environment.rb +106 -0
  11. data/lib/exe/xtractr +0 -0
  12. data/lib/mu/pcap.rb +110 -0
  13. data/lib/mu/pcap/ethernet.rb +148 -0
  14. data/lib/mu/pcap/header.rb +75 -0
  15. data/lib/mu/pcap/io_pair.rb +67 -0
  16. data/lib/mu/pcap/io_wrapper.rb +76 -0
  17. data/lib/mu/pcap/ip.rb +61 -0
  18. data/lib/mu/pcap/ipv4.rb +257 -0
  19. data/lib/mu/pcap/ipv6.rb +148 -0
  20. data/lib/mu/pcap/packet.rb +104 -0
  21. data/lib/mu/pcap/pkthdr.rb +155 -0
  22. data/lib/mu/pcap/reader.rb +61 -0
  23. data/lib/mu/pcap/reader/http_family.rb +170 -0
  24. data/lib/mu/pcap/sctp.rb +367 -0
  25. data/lib/mu/pcap/sctp/chunk.rb +123 -0
  26. data/lib/mu/pcap/sctp/chunk/data.rb +134 -0
  27. data/lib/mu/pcap/sctp/chunk/init.rb +100 -0
  28. data/lib/mu/pcap/sctp/chunk/init_ack.rb +68 -0
  29. data/lib/mu/pcap/sctp/parameter.rb +110 -0
  30. data/lib/mu/pcap/sctp/parameter/ip_address.rb +48 -0
  31. data/lib/mu/pcap/stream_packetizer.rb +72 -0
  32. data/lib/mu/pcap/tcp.rb +505 -0
  33. data/lib/mu/pcap/udp.rb +69 -0
  34. data/lib/mu/scenario/pcap.rb +164 -0
  35. data/lib/mu/scenario/pcap/fields.rb +50 -0
  36. data/lib/mu/scenario/pcap/rtp.rb +71 -0
  37. data/lib/pcapr_local.rb +159 -0
  38. data/lib/pcapr_local/config.rb +336 -0
  39. data/lib/pcapr_local/db.rb +197 -0
  40. data/lib/pcapr_local/scanner.rb +250 -0
  41. data/lib/pcapr_local/server.rb +178 -0
  42. data/lib/pcapr_local/www/favicon.ico +0 -0
  43. data/lib/pcapr_local/www/favicon.png +0 -0
  44. data/lib/pcapr_local/www/home/index.html +138 -0
  45. data/lib/pcapr_local/www/static/image/16x16/Cancel.png +0 -0
  46. data/lib/pcapr_local/www/static/image/16x16/Cancel.png.1 +0 -0
  47. data/lib/pcapr_local/www/static/image/16x16/Download.png +0 -0
  48. data/lib/pcapr_local/www/static/image/16x16/Folder3.png +0 -0
  49. data/lib/pcapr_local/www/static/image/16x16/Full Size.png +0 -0
  50. data/lib/pcapr_local/www/static/image/16x16/Minus.png +0 -0
  51. data/lib/pcapr_local/www/static/image/16x16/Plus.png +0 -0
  52. data/lib/pcapr_local/www/static/image/16x16/Search.png +0 -0
  53. data/lib/pcapr_local/www/static/image/16x16/User.png +0 -0
  54. data/lib/pcapr_local/www/static/image/48x48/Phone.png +0 -0
  55. data/lib/pcapr_local/www/static/image/48x48/Video.png +0 -0
  56. data/lib/pcapr_local/www/static/image/bar-orange.gif +0 -0
  57. data/lib/pcapr_local/www/static/image/beta.png +0 -0
  58. data/lib/pcapr_local/www/static/image/bg.png +0 -0
  59. data/lib/pcapr_local/www/static/image/blockquote.png +0 -0
  60. data/lib/pcapr_local/www/static/image/body-bg.png +0 -0
  61. data/lib/pcapr_local/www/static/image/body-h3.png +0 -0
  62. data/lib/pcapr_local/www/static/image/body-hl1-bg.png +0 -0
  63. data/lib/pcapr_local/www/static/image/body-hl1-h3.png +0 -0
  64. data/lib/pcapr_local/www/static/image/body-hl1-readmore.png +0 -0
  65. data/lib/pcapr_local/www/static/image/body-hl2-bg.png +0 -0
  66. data/lib/pcapr_local/www/static/image/body-hl2-h3.png +0 -0
  67. data/lib/pcapr_local/www/static/image/body-hl2-readmore.png +0 -0
  68. data/lib/pcapr_local/www/static/image/body-hl3-bg.png +0 -0
  69. data/lib/pcapr_local/www/static/image/body-hl3-h3.png +0 -0
  70. data/lib/pcapr_local/www/static/image/body-hl3-readmore.png +0 -0
  71. data/lib/pcapr_local/www/static/image/body-hl4-bg.png +0 -0
  72. data/lib/pcapr_local/www/static/image/body-hl4-h3.png +0 -0
  73. data/lib/pcapr_local/www/static/image/body-hl4-readmore.png +0 -0
  74. data/lib/pcapr_local/www/static/image/body-hl5-h3.png +0 -0
  75. data/lib/pcapr_local/www/static/image/body-hl6-h3.png +0 -0
  76. data/lib/pcapr_local/www/static/image/body-hl7-h3.png +0 -0
  77. data/lib/pcapr_local/www/static/image/body-hl8-h3.png +0 -0
  78. data/lib/pcapr_local/www/static/image/body-readmore.png +0 -0
  79. data/lib/pcapr_local/www/static/image/bottom-bg.png +0 -0
  80. data/lib/pcapr_local/www/static/image/bottom-l.png +0 -0
  81. data/lib/pcapr_local/www/static/image/bottom-r.png +0 -0
  82. data/lib/pcapr_local/www/static/image/btn-search.png +0 -0
  83. data/lib/pcapr_local/www/static/image/bullet-1.png +0 -0
  84. data/lib/pcapr_local/www/static/image/bullet-2.png +0 -0
  85. data/lib/pcapr_local/www/static/image/bullet-3.png +0 -0
  86. data/lib/pcapr_local/www/static/image/bullet-4.png +0 -0
  87. data/lib/pcapr_local/www/static/image/bullet-5.png +0 -0
  88. data/lib/pcapr_local/www/static/image/bullet-6.png +0 -0
  89. data/lib/pcapr_local/www/static/image/bullet-7.png +0 -0
  90. data/lib/pcapr_local/www/static/image/bullet-hl1.png +0 -0
  91. data/lib/pcapr_local/www/static/image/bullet-hl2.png +0 -0
  92. data/lib/pcapr_local/www/static/image/bullet-hl3.png +0 -0
  93. data/lib/pcapr_local/www/static/image/bullet-hl4.png +0 -0
  94. data/lib/pcapr_local/www/static/image/bullet-pathway.png +0 -0
  95. data/lib/pcapr_local/www/static/image/bullet-section1.png +0 -0
  96. data/lib/pcapr_local/www/static/image/bullet-section2.png +0 -0
  97. data/lib/pcapr_local/www/static/image/collapsed.gif +0 -0
  98. data/lib/pcapr_local/www/static/image/crosslink.png +0 -0
  99. data/lib/pcapr_local/www/static/image/expanded.gif +0 -0
  100. data/lib/pcapr_local/www/static/image/favicon.ico +0 -0
  101. data/lib/pcapr_local/www/static/image/favicon.png +0 -0
  102. data/lib/pcapr_local/www/static/image/icon-author.png +0 -0
  103. data/lib/pcapr_local/www/static/image/icon-created.png +0 -0
  104. data/lib/pcapr_local/www/static/image/p-expand.gif +0 -0
  105. data/lib/pcapr_local/www/static/image/pcapr-logo.png +0 -0
  106. data/lib/pcapr_local/www/static/image/powered-by.png +0 -0
  107. data/lib/pcapr_local/www/static/image/section1-bg.png +0 -0
  108. data/lib/pcapr_local/www/static/image/section1-h3.png +0 -0
  109. data/lib/pcapr_local/www/static/image/section1-readmore.png +0 -0
  110. data/lib/pcapr_local/www/static/image/section2-bg.png +0 -0
  111. data/lib/pcapr_local/www/static/image/section2-h3.png +0 -0
  112. data/lib/pcapr_local/www/static/image/section2-readmore.png +0 -0
  113. data/lib/pcapr_local/www/static/image/status-alert.png +0 -0
  114. data/lib/pcapr_local/www/static/image/status-download.png +0 -0
  115. data/lib/pcapr_local/www/static/image/status-info.png +0 -0
  116. data/lib/pcapr_local/www/static/image/status-note.png +0 -0
  117. data/lib/pcapr_local/www/static/image/tab-round.png +0 -0
  118. data/lib/pcapr_local/www/static/image/throbber.gif +0 -0
  119. data/lib/pcapr_local/www/static/image/user.jpg +0 -0
  120. data/lib/pcapr_local/www/static/script/closet/async.js +421 -0
  121. data/lib/pcapr_local/www/static/script/closet/closet.api.js +241 -0
  122. data/lib/pcapr_local/www/static/script/closet/closet.folders.js +94 -0
  123. data/lib/pcapr_local/www/static/script/closet/closet.js +187 -0
  124. data/lib/pcapr_local/www/static/script/closet/closet.mr.js +219 -0
  125. data/lib/pcapr_local/www/static/script/closet/closet.options.js +359 -0
  126. data/lib/pcapr_local/www/static/script/closet/closet.quantity.js +73 -0
  127. data/lib/pcapr_local/www/static/script/closet/closet.render.js +205 -0
  128. data/lib/pcapr_local/www/static/script/closet/closet.report.js +86 -0
  129. data/lib/pcapr_local/www/static/script/closet/closet.reports.http.js +135 -0
  130. data/lib/pcapr_local/www/static/script/closet/closet.reports.overview.js +163 -0
  131. data/lib/pcapr_local/www/static/script/closet/closet.reports.sip.js +159 -0
  132. data/lib/pcapr_local/www/static/script/closet/closet.reports.tcp.js +72 -0
  133. data/lib/pcapr_local/www/static/script/closet/closet.reports.visualize.js +263 -0
  134. data/lib/pcapr_local/www/static/script/closet/closet.util.js +40 -0
  135. data/lib/pcapr_local/www/static/script/jquery/jquery-1.4.2.min.js +154 -0
  136. data/lib/pcapr_local/www/static/script/jquery/jquery-ui.js +10921 -0
  137. data/lib/pcapr_local/www/static/script/jquery/jquery.flot.js +2123 -0
  138. data/lib/pcapr_local/www/static/script/jquery/jquery.flot.selection.js +184 -0
  139. data/lib/pcapr_local/www/static/script/jquery/jquery.flot.stack.js +184 -0
  140. data/lib/pcapr_local/www/static/script/jquery/jquery.form.js +643 -0
  141. data/lib/pcapr_local/www/static/script/jquery/jquery.jsonp.min.js +3 -0
  142. data/lib/pcapr_local/www/static/script/jquery/jquery.menu.js +142 -0
  143. data/lib/pcapr_local/www/static/script/jquery/jquery.suggest.js +308 -0
  144. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.core.js +203 -0
  145. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.slider.js +629 -0
  146. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.sortable.js +1055 -0
  147. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.widget.js +236 -0
  148. data/lib/pcapr_local/www/static/script/json2.js +481 -0
  149. data/lib/pcapr_local/www/static/script/sammy/plugins/sammy.cache.js +115 -0
  150. data/lib/pcapr_local/www/static/script/sammy/plugins/sammy.template.js +117 -0
  151. data/lib/pcapr_local/www/static/script/sammy/sammy.js +1696 -0
  152. data/lib/pcapr_local/www/static/script/tipsy/jquery.tipsy.js +104 -0
  153. data/lib/pcapr_local/www/static/style/c3p0.css +116 -0
  154. data/lib/pcapr_local/www/static/style/jquery.suggest.css +27 -0
  155. data/lib/pcapr_local/www/static/style/page.css +1113 -0
  156. data/lib/pcapr_local/www/static/style/tipsy.css +7 -0
  157. data/lib/pcapr_local/www/templates/browse.services.template +10 -0
  158. data/lib/pcapr_local/www/templates/browse.template +77 -0
  159. data/lib/pcapr_local/www/templates/flows.template +38 -0
  160. data/lib/pcapr_local/www/templates/pcap.template +63 -0
  161. data/lib/pcapr_local/www/templates/sip.calls.template +35 -0
  162. data/lib/pcapr_local/www/templates/statistics.template +6 -0
  163. data/lib/pcapr_local/xtractr.rb +179 -0
  164. data/lib/pcapr_local/xtractr/instance.rb +172 -0
  165. data/pcapr-local.gemspec +297 -0
  166. data/test/mu/pcap/reader/tc_http_family.rb +251 -0
  167. data/test/mu/pcap/tc_ethernet.rb +71 -0
  168. data/test/mu/pcap/tc_header.rb +56 -0
  169. data/test/mu/pcap/tc_ipv4.rb +103 -0
  170. data/test/mu/pcap/tc_ipv6.rb +83 -0
  171. data/test/mu/pcap/tc_packet.rb +44 -0
  172. data/test/mu/pcap/tc_pair.rb +58 -0
  173. data/test/mu/pcap/tc_pkthdr.rb +33 -0
  174. data/test/mu/pcap/tc_reader.rb +76 -0
  175. data/test/mu/pcap/tc_tcp.rb +426 -0
  176. data/test/mu/pcap/tc_udp.rb +33 -0
  177. data/test/mu/pcap/tc_wrapper.rb +80 -0
  178. data/test/mu/scenario/pcap/tc_fields.rb +67 -0
  179. data/test/mu/scenario/pcap/tc_rtp.rb +135 -0
  180. data/test/mu/scenario/sip_signalled_call_1.pcap +0 -0
  181. data/test/mu/scenario/tc_pcap.rb +190 -0
  182. data/test/mu/scenario/test_data/arp.pcap +0 -0
  183. data/test/mu/scenario/test_data/dns.pcap +0 -0
  184. data/test/mu/scenario/test_data/http-v6.pcap +0 -0
  185. data/test/mu/scenario/test_data/http.pcap +0 -0
  186. data/test/mu/scenario/test_data/http_chunked.pcap +0 -0
  187. data/test/mu/scenario/test_data/http_deflate.pcap +0 -0
  188. data/test/mu/scenario/test_data/httpauth3.pcap +0 -0
  189. data/test/mu/scenario/test_data/icmp.pcap +0 -0
  190. data/test/mu/scenario/test_data/sip_signalled_call_1.pcap +0 -0
  191. data/test/mu/tc_pcap.rb +39 -0
  192. data/test/mu/testcase.rb +86 -0
  193. data/test/pcapr_local/arp.pcap +0 -0
  194. data/test/pcapr_local/data.js +3 -0
  195. data/test/pcapr_local/http_chunked.pcap +0 -0
  196. data/test/pcapr_local/tc_api.rb +181 -0
  197. data/test/pcapr_local/test.tgz +0 -0
  198. data/test/pcapr_local/test_scanner.rb +241 -0
  199. data/test/pcapr_local/test_xtractr.rb +219 -0
  200. data/test/pcapr_local/testcase.rb +107 -0
  201. data/test/test_export_to_scenario.sh +25 -0
  202. data/test/test_pcapr_local.rb +29 -0
  203. 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"/>&nbsp;</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>