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,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
|