pcapr-local 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
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,336 @@
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ module PcaprLocal
6
+ module Config
7
+
8
+ HOME = ENV["HOME"] || File.expand_path('./')
9
+
10
+ DEFAULT_CONFIG = {
11
+ # Shared config.
12
+ "install_dir" => "#{HOME}/pcapr.Local/",
13
+ "pcap_dir" => "#{HOME}/pcapr.Local/pcaps",
14
+ "index_dir" => "#{HOME}/pcapr.Local/indexes",
15
+
16
+ # UI (Sinatra)
17
+ "app" => {
18
+ "host" => '127.0.0.1',
19
+ "port" => 8080,
20
+ },
21
+
22
+ # Pcap scanning
23
+ "scanner" => {
24
+ "interval" => 60, # scan every n seconds.
25
+ "queue_delay" => 60, # skip file if was modified in last n seconds.
26
+ },
27
+
28
+ # Couch
29
+ "couch" => {
30
+ "uri" => 'http://127.0.0.1:5984/',
31
+ "database" => "pcapr_local"
32
+ },
33
+
34
+ # Xtractr
35
+ "xtractr" => {
36
+ "path" => 'xtractr',
37
+ "idle_timeout" => 60 # kill xtractr browser after n seconds of idle time
38
+ }
39
+ }
40
+
41
+
42
+ # Tuple of required dependencies and ubuntu package name
43
+ REQUIRED_EXES = [
44
+ ["tshark", "tshark"],
45
+ ["zip", "zip"],
46
+ ]
47
+
48
+ def self.assert_environment
49
+ check_platform
50
+
51
+ REQUIRED_EXES.each do |exe_package|
52
+ check_exe *exe_package
53
+ end
54
+ end
55
+
56
+ def self.check_exe exe, package_name
57
+ if not system "sh -c 'type #{exe} > /dev/null 2>&1'"
58
+ $stderr.puts <<HERE
59
+
60
+ pcapr.Local requires the #{exe} executable to function but #{exe} is either not
61
+ installed or cannot be found in your PATH. Please install #{exe} and ensure
62
+ that it is in your PATH.
63
+
64
+ HERE
65
+
66
+ if system "sh -c 'type apt-get > /dev/null 2>&1'"
67
+ $stderr.puts <<HERE
68
+ If #{exe} is not installed you may be able to install it with:
69
+ 'sudo apt-get install #{package_name}'
70
+
71
+ HERE
72
+ end
73
+
74
+ exit 1
75
+ end
76
+ end
77
+
78
+ # Exit unless we are on Linux (the xtractr exe is linux only).
79
+ def self.check_platform
80
+ if not RUBY_PLATFORM =~ /linux/i
81
+ $stderr.puts "Sorry, pcapr.Local only runs on linux :("
82
+ exit 1
83
+ end
84
+ end
85
+
86
+
87
+ # Return configuration as a Hash. Optionally takes an external
88
+ # configuration file.
89
+ def self.user_config_path
90
+ raise "HOME environment variable is not set" unless ENV['HOME']
91
+ config_dir = File.join(ENV['HOME'], '.pcapr_local')
92
+ File.join(config_dir, 'config')
93
+ end
94
+
95
+ def self.config config_path=nil
96
+ config_path ||= user_config_path
97
+
98
+ if not File.exist? config_path
99
+ self.create_config_file config_path
100
+ end
101
+
102
+ config = DEFAULT_CONFIG.dup
103
+ begin
104
+ user_config = JSON.parse(File.read(config_path))
105
+ rescue JSON::ParserError => e
106
+ raise "Config file is not well formed JSON, please correct or delete #{config_path}"
107
+ end
108
+ config = config_merge(config, user_config)
109
+
110
+ # Derived config (not persisted)
111
+ config['pidfile'] = File.join(config.fetch('install_dir'), '.server.pid')
112
+ config['log_dir'] = File.join(config.fetch('install_dir'), 'log')
113
+
114
+ return config
115
+ end
116
+
117
+ private
118
+
119
+ def self.create_config_file config_path
120
+ config = get_user_config
121
+ FileUtils.mkdir_p File.dirname(config_path)
122
+ File.open config_path, 'w' do |f|
123
+ f.puts JSON.pretty_generate(config)
124
+ end
125
+ puts "\nThank you. Configuration is saved at #{config_path}."
126
+ sleep 2
127
+ end
128
+
129
+ # Recursively applies updates in update config to start config.
130
+ # Does not apply updates unless they are already present in the
131
+ # starting config
132
+ def self.config_merge start, update
133
+ start = start.dup
134
+ start.to_a.each do |key, val|
135
+ next unless update.include? key
136
+ update_val = update[key]
137
+ if val.is_a? Hash
138
+ if update_val.is_a? Hash
139
+ # Both values are hashes, recurse.
140
+ start[key] = config_merge(val, update_val)
141
+ else
142
+ # Update is not expected type, ignore it.
143
+ end
144
+ else
145
+ start[key] = update_val
146
+ end
147
+ end
148
+ return start
149
+ end
150
+
151
+ Opt = Struct.new :key, :default, :validate, :question
152
+
153
+ # Interactively gathers configuration from the user and returns config hash.
154
+ def self.get_user_config
155
+ config = JSON.parse(DEFAULT_CONFIG.to_json)
156
+ user_opts = []
157
+
158
+ # install dir
159
+ pcap_dir = Opt.new "install_dir"
160
+ pcap_dir.question = "Where should pcapr.Local store user files?"
161
+ pcap_dir.default = "#{HOME}/pcapr.Local"
162
+ pcap_dir.validate = :dir
163
+ user_opts << pcap_dir
164
+
165
+ # pcap dir
166
+ pcap_dir = Opt.new "pcap_dir"
167
+ pcap_dir.question = "Which directory would you like to scan for indexable pcaps?"
168
+ pcap_dir.default = Proc.new { File.join(config["install_dir"], 'pcaps') }
169
+ pcap_dir.validate = :dir
170
+ user_opts << pcap_dir
171
+
172
+ # index dir
173
+ index_dir = Opt.new "index_dir"
174
+ index_dir.question = "Where would you like to store index files?"
175
+ index_dir.default = Proc.new { File.join(config["install_dir"], 'indexes') }
176
+ index_dir.validate = :dir
177
+ user_opts << index_dir
178
+
179
+ # host
180
+ app_host = Opt.new "app.host"
181
+ app_host.question = "What IP address should pcapr.Local run on? Use 0.0.0.0 to listen on all interfaces."
182
+ app_host.default = "127.0.0.1"
183
+ app_host.validate = :app_host
184
+ user_opts << app_host
185
+
186
+ # port
187
+ app_port = Opt.new "app.port"
188
+ app_port.question = "What port should pcapr.Local listen on?"
189
+ app_port.default = "8080"
190
+ app_port.validate = :app_port
191
+ user_opts << app_port
192
+
193
+ # CouchDB database name
194
+ database = Opt.new "couch.database"
195
+ database.question = "Pick a name for your CouchDB database (database will be created automatically)."
196
+ user = ENV['LOGNAME'] || Process.uid
197
+ database.default = "pcapr_local_#{user}"
198
+ database.validate = :db_name
199
+ user_opts << database
200
+
201
+ # CouchDB server
202
+ couch_uri = Opt.new "couch.uri"
203
+ couch_uri.question = "pcapr.Local requires CouchDB to run. Where is your CouchDB server?"
204
+ couch_uri.default = "http://127.0.0.1:5984"
205
+ couch_uri.validate = :couch_uri
206
+ user_opts << couch_uri
207
+
208
+ user_opts.each do |opt|
209
+ ask_user opt, config
210
+ end
211
+
212
+ return config
213
+ end
214
+
215
+ def self.ask_user opt, config
216
+ stty_save = `stty -g`.chomp
217
+ begin
218
+ # Ask question.
219
+ puts "", opt.question
220
+
221
+ # Show default value in prompt and get answer.
222
+ default = opt.default
223
+ if opt.default.is_a? Proc
224
+ default = opt.default.call
225
+ else
226
+ default = opt.default
227
+ end
228
+ choice = Readline.readline("[#{default}] ").strip
229
+ if choice.empty?
230
+ choice = default
231
+ end
232
+
233
+ # Validate and possibly change answer.
234
+ if opt.validate
235
+ choice = Validate.send(opt.validate, choice, config)
236
+ end
237
+ rescue Validate::Error => e
238
+ puts "\nError: #{e.message}"
239
+ retry
240
+ rescue Interrupt
241
+ system("stty", stty_save)
242
+ puts "\naborting"
243
+ exit 1
244
+ end
245
+
246
+ opt_set config, opt.key, choice
247
+ end
248
+
249
+ # Navigates nested hashes to set options in the form "foo.bar.baz"
250
+ def self.opt_set hash, dotted_name, value
251
+ keys = dotted_name.split('.')
252
+ last_key = keys.pop
253
+ keys.each {|k| hash = hash[k] }
254
+ hash[last_key] = value
255
+ end
256
+
257
+ module Validate
258
+ class Error < StandardError ; end
259
+
260
+ def self.app_host host, config
261
+ begin
262
+ server = TCPServer.new host, 0
263
+ server.close
264
+ rescue => e
265
+ raise Error, "Got error '#{e.message}' when trying to create server for this host. Please pick a different host."
266
+ end
267
+ return host
268
+ end
269
+
270
+ def self.dir dir, config
271
+ begin
272
+ dir = File.expand_path dir
273
+ FileUtils.mkdir_p dir
274
+ rescue
275
+ raise Error, "Directory (#{dir}) could not be created. Please choose a different directory."
276
+ end
277
+ return dir
278
+ end
279
+
280
+ def self.app_port port, config
281
+ begin
282
+ port = Integer(port)
283
+ rescue
284
+ raise Error, "'#{port}' is not a valid port."
285
+ end
286
+ begin
287
+ server = TCPServer.new config["app_host"], port
288
+ server.close
289
+ rescue => e
290
+ raise Error, "Got error '#{e.message}' when trying to listen on #{config['app_host']}:#{port}."
291
+ end
292
+ return port
293
+ end
294
+
295
+ def self.db_name name, config
296
+ unless name =~ /\A[a-zA-Z0-9_]+\Z/
297
+ raise Error, "Database name can include only letters numbers and underscores."
298
+ end
299
+ return name
300
+ end
301
+
302
+ def self.couch_uri uri, config
303
+ # Check couch install is reachable.
304
+ begin
305
+ server = CouchRest::Server.new uri
306
+ server.info
307
+ rescue => e
308
+ err = "Could not connect to couchdb at #{uri}. Got error '#{e.message}'\n"
309
+ if system('which apt-get > /dev/null')
310
+ err << "If CouchDB is not installed you may be able to install it with:\n"
311
+ err << " 'sudo apt-get install couchdb'"
312
+ else
313
+ err << "Is CouchDB installed?"
314
+ end
315
+ raise Error, err
316
+ end
317
+
318
+ # Check that credentials are sufficient by actually creating the database.
319
+ db_name = config['couch']['database']
320
+ begin
321
+ db = DB.get_db "uri" => uri, "database" => db_name, "host" => 'foo', "port" => 3
322
+ rescue RestClient::Exception => e
323
+ err = "Got '#{e.message}' while creating database.\n"
324
+ err << "If you have authentication enabled in CouchDB, please include username and\n"
325
+ err << "password in the URI like:\n"
326
+ err << " http://user:password@127.0.0.1:5984\n"
327
+ raise Error, err
328
+ end
329
+ return uri
330
+ end
331
+ end
332
+ end
333
+ end
334
+
335
+
336
+
@@ -0,0 +1,197 @@
1
+ # http://www.mudynamics.com
2
+ # http://labs.mudynamics.com
3
+ # http://www.pcapr.net
4
+
5
+ require 'couchrest'
6
+
7
+ module PcaprLocal
8
+ class DB
9
+ def self.get_db config
10
+ database = config.fetch "database"
11
+ base = config.fetch "uri"
12
+
13
+ couch_url = File.join(base, database)
14
+ db = CouchRest.database! couch_url
15
+ patch_db db
16
+
17
+ begin
18
+ design = db.get("_design/pcaps")
19
+ rescue RestClient::ResourceNotFound
20
+ design = CouchRest::Design.new
21
+ design.name = "pcaps"
22
+ design.database = db
23
+ db.save_doc design
24
+ end
25
+
26
+ db.update_doc "_design/pcaps" do |design|
27
+ design['views'] = self.views
28
+ design
29
+ end
30
+
31
+ db
32
+ end
33
+
34
+ # Patch couch to include create and update times for each document.
35
+ def self.patch_db pcapr_local
36
+ def pcapr_local.update_doc id, &block
37
+ super(id) do |doc|
38
+ doc = block.call doc
39
+ doc['updated_at'] = Time.now
40
+ doc
41
+ end
42
+ end
43
+
44
+ def pcapr_local.save_doc *args
45
+ doc = args[0]
46
+ now = Time.now
47
+ doc["created_at"] ||= now
48
+ doc["updated_at"] ||= now
49
+ super
50
+ end
51
+
52
+ # Performs a query and yields each row to the the supplied block.
53
+ # Uses paging to split the query into page_size chunks.
54
+ def pcapr_local.each_in_view view, query=nil, page_size=50 #block
55
+ query ||= {}
56
+ limit = page_size + 1
57
+ query[:limit] = limit
58
+
59
+ begin
60
+ res = self.view(view, query)
61
+ rows = res['rows']
62
+ next_doc = rows.size == limit ? rows.pop : nil
63
+ if next_doc
64
+ query[:startkey] = next_doc['key']
65
+ query[:startkey_docid] = next_doc['id']
66
+ end
67
+
68
+ rows.each do |row|
69
+ yield row
70
+ end
71
+ end while next_doc
72
+ end
73
+
74
+ nil
75
+ end
76
+
77
+ def self.views
78
+ {
79
+ "by_created_at" => {
80
+ "map" => %q(function(doc) {
81
+ if (doc.type === 'pcap') {
82
+ emit(Date.parse(doc.created_at), null);
83
+ }
84
+ })
85
+ },
86
+ "by_inode" => {
87
+ "map" => %q(function(doc) {
88
+ if (doc.type === 'pcap') {
89
+ emit(doc.stat.inode, null);
90
+ }
91
+ })
92
+ },
93
+ "by_filename" => {
94
+ "map" => %q(function(doc) {
95
+ if (doc.type === 'pcap') {
96
+ emit(doc.filename, null);
97
+ }
98
+ })
99
+ },
100
+ "by_directory" => {
101
+ "map" => %q(function(doc) {
102
+ if (doc.type === 'pcap') {
103
+ var paths = doc.filename.split('/');
104
+ paths.unshift(paths.length-1);
105
+ paths.pop();
106
+ paths.push(Date.parse(doc.created_at));
107
+ emit(paths,1);
108
+ }
109
+ }),
110
+ "reduce" => '_sum'
111
+ },
112
+ "by_path" => {
113
+ "map" => %q(function(doc) {
114
+ if (doc.type === 'pcap') {
115
+ var paths = doc.filename.split('/');
116
+ paths.pop();
117
+ emit(paths,1);
118
+ }
119
+ }),
120
+ "reduce" => '_sum'
121
+ },
122
+ "by_service" => {
123
+ "map" => %q(function(doc) {
124
+ if (doc.type === 'pcap' && doc.index) {
125
+ var services = doc.index.services;
126
+ for (var i=0; i<services.length; ++i) {
127
+ emit([ services[i], Date.parse(doc.created_at) ], 1);
128
+ }
129
+ }
130
+ }),
131
+ "reduce" => '_sum'
132
+ },
133
+ "indexed" => {
134
+ "map" => %q(function(doc) {
135
+ if (doc.type === 'pcap') {
136
+ if (doc.status === 'indexed') {
137
+ emit(doc.filename, null);
138
+ }
139
+ }
140
+ })
141
+ },
142
+ "queued" => {
143
+ "map" => %q(function(doc) {
144
+ if (doc.type === 'pcap') {
145
+ if (doc.status === 'queued' || doc.status === 'indexing') {
146
+ emit(doc.filename, null);
147
+ }
148
+ }
149
+ })
150
+ },
151
+ "by_status" => {
152
+ "map" => %q(function(doc) {
153
+ if (doc.type === 'pcap') {
154
+ emit([ doc.status, Date.parse(doc.created_at) ], 1);
155
+ }
156
+ }),
157
+ "reduce" => '_sum'
158
+ },
159
+ "by_keyword" => {
160
+ "map" => %q(function(doc) {
161
+ if (doc.type === 'pcap') {
162
+ var keywords = {};
163
+ var paths = doc.filename.split('/');
164
+ for (var p in paths) {
165
+ var tokens = paths[p].toLowerCase().split(/[ -_\.\\\/\(\)]/);
166
+ for (var t in tokens) {
167
+ if (tokens[t].length > 2) {
168
+ keywords[tokens[t]] = true;
169
+ }
170
+ }
171
+ }
172
+
173
+ for (var k in keywords) {
174
+ emit([ k, Date.parse(doc.created_at) ], 1);
175
+ }
176
+ }
177
+ }),
178
+ "reduce" => '_sum'
179
+ },
180
+ "statistics" => {
181
+ "map" => %q(function(doc) {
182
+ if (doc.type === 'pcap') {
183
+ emit('pcaps', 1);
184
+ emit('bytes', doc.stat.size);
185
+ if (doc.index) {
186
+ emit('packets', doc.index.about.packets);
187
+ emit('flows', doc.index.about.flows);
188
+ emit('services', doc.index.about.services);
189
+ }
190
+ }
191
+ }),
192
+ "reduce" => '_sum'
193
+ }
194
+ }
195
+ end
196
+ end
197
+ end