patronus_fati 0.8.0
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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE +165 -0
- data/README.md +29 -0
- data/Rakefile +21 -0
- data/bin/patronus_fati +54 -0
- data/kismet_configs/pat_fat_startup.sh +3 -0
- data/kismet_configs/patronus_fati_kismet.conf +88 -0
- data/lib/patronus_fati/cap_struct.rb +97 -0
- data/lib/patronus_fati/connection.rb +85 -0
- data/lib/patronus_fati/consts.rb +85 -0
- data/lib/patronus_fati/data_mapper/crypt_flags.rb +83 -0
- data/lib/patronus_fati/data_mapper/null_table_prefix.rb +7 -0
- data/lib/patronus_fati/data_models/access_point.rb +74 -0
- data/lib/patronus_fati/data_models/alert.rb +24 -0
- data/lib/patronus_fati/data_models/ap_frequency.rb +14 -0
- data/lib/patronus_fati/data_models/ap_signal.rb +12 -0
- data/lib/patronus_fati/data_models/client.rb +69 -0
- data/lib/patronus_fati/data_models/client_frequency.rb +14 -0
- data/lib/patronus_fati/data_models/client_signal.rb +12 -0
- data/lib/patronus_fati/data_models/common.rb +49 -0
- data/lib/patronus_fati/data_models/connection.rb +48 -0
- data/lib/patronus_fati/data_models/mac.rb +48 -0
- data/lib/patronus_fati/data_models/probe.rb +13 -0
- data/lib/patronus_fati/data_models/ssid.rb +35 -0
- data/lib/patronus_fati/data_observers/access_point_observer.rb +53 -0
- data/lib/patronus_fati/data_observers/alert_observer.rb +12 -0
- data/lib/patronus_fati/data_observers/client_observer.rb +52 -0
- data/lib/patronus_fati/data_observers/connection_observer.rb +66 -0
- data/lib/patronus_fati/data_observers/probe_observer.rb +11 -0
- data/lib/patronus_fati/data_observers/ssid_observer.rb +53 -0
- data/lib/patronus_fati/event_handler.rb +27 -0
- data/lib/patronus_fati/factory_base.rb +56 -0
- data/lib/patronus_fati/message_models/ack.rb +5 -0
- data/lib/patronus_fati/message_models/alert.rb +10 -0
- data/lib/patronus_fati/message_models/battery.rb +6 -0
- data/lib/patronus_fati/message_models/bssid.rb +43 -0
- data/lib/patronus_fati/message_models/bssidsrc.rb +15 -0
- data/lib/patronus_fati/message_models/btscandev.rb +11 -0
- data/lib/patronus_fati/message_models/capability.rb +5 -0
- data/lib/patronus_fati/message_models/channel.rb +13 -0
- data/lib/patronus_fati/message_models/client.rb +45 -0
- data/lib/patronus_fati/message_models/clisrc.rb +17 -0
- data/lib/patronus_fati/message_models/clitag.rb +6 -0
- data/lib/patronus_fati/message_models/common.rb +15 -0
- data/lib/patronus_fati/message_models/critfail.rb +5 -0
- data/lib/patronus_fati/message_models/error.rb +6 -0
- data/lib/patronus_fati/message_models/gps.rb +6 -0
- data/lib/patronus_fati/message_models/info.rb +11 -0
- data/lib/patronus_fati/message_models/kismet.rb +8 -0
- data/lib/patronus_fati/message_models/nettag.rb +6 -0
- data/lib/patronus_fati/message_models/packet.rb +10 -0
- data/lib/patronus_fati/message_models/plugin.rb +8 -0
- data/lib/patronus_fati/message_models/protocols.rb +5 -0
- data/lib/patronus_fati/message_models/remove.rb +6 -0
- data/lib/patronus_fati/message_models/source.rb +10 -0
- data/lib/patronus_fati/message_models/spectrum.rb +8 -0
- data/lib/patronus_fati/message_models/ssid.rb +25 -0
- data/lib/patronus_fati/message_models/status.rb +5 -0
- data/lib/patronus_fati/message_models/string.rb +6 -0
- data/lib/patronus_fati/message_models/terminate.rb +5 -0
- data/lib/patronus_fati/message_models/time.rb +6 -0
- data/lib/patronus_fati/message_models/trackinfo.rb +8 -0
- data/lib/patronus_fati/message_models/wepkey.rb +6 -0
- data/lib/patronus_fati/message_models.rb +39 -0
- data/lib/patronus_fati/message_parser.rb +44 -0
- data/lib/patronus_fati/message_processor/alert.rb +15 -0
- data/lib/patronus_fati/message_processor/bssid.rb +47 -0
- data/lib/patronus_fati/message_processor/capability.rb +24 -0
- data/lib/patronus_fati/message_processor/client.rb +55 -0
- data/lib/patronus_fati/message_processor/critfail.rb +8 -0
- data/lib/patronus_fati/message_processor/error.rb +7 -0
- data/lib/patronus_fati/message_processor/protocols.rb +7 -0
- data/lib/patronus_fati/message_processor/ssid.rb +48 -0
- data/lib/patronus_fati/message_processor.rb +52 -0
- data/lib/patronus_fati/version.rb +3 -0
- data/lib/patronus_fati.rb +68 -0
- data/patronus_fati.gemspec +41 -0
- data/spec/data_models/access_point_spec.rb +26 -0
- data/spec/data_models/alert_spec.rb +12 -0
- data/spec/data_models/client_spec.rb +25 -0
- data/spec/data_models/connection_spec.rb +86 -0
- data/spec/data_models/mac_spec.rb +26 -0
- data/spec/patronus_fati_spec.rb +13 -0
- data/spec/spec_helper.rb +71 -0
- data/wrapper.rb +19 -0
- metadata +393 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module PatronusFati
|
|
2
|
+
module MessageParser
|
|
3
|
+
# We receive some messages before we specifically request the abilities of
|
|
4
|
+
# the server, when this happens we'll attempt to map the data using the
|
|
5
|
+
# default attribute ordering that was provided by the Kismet server this
|
|
6
|
+
# client was coded against, this may not be entirely accurate, but will
|
|
7
|
+
# become accurate before we receive any meaningful data.
|
|
8
|
+
def self.parse(msg)
|
|
9
|
+
return unless (raw_data = handle_msg(msg))
|
|
10
|
+
|
|
11
|
+
unless (cap = get_model(raw_data[0]))
|
|
12
|
+
warn('Message received had unknown message type: ' + raw_data[0])
|
|
13
|
+
return
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
src_keys = cap.enabled_keys.empty? ? cap.attribute_keys : cap.enabled_keys
|
|
17
|
+
cap.new(Hash[src_keys.zip(raw_data[1])])
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
protected
|
|
21
|
+
|
|
22
|
+
def self.extract_data(data_line)
|
|
23
|
+
data_line.scan(PatronusFati::DATA_DELIMITER).map { |a, b| (a || b).tr("\x01", '') }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.get_model(mdl)
|
|
27
|
+
return unless PatronusFati::MessageModels.const_defined?(model_name(mdl))
|
|
28
|
+
PatronusFati::MessageModels.const_get(model_name(mdl))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.handle_msg(line)
|
|
32
|
+
resp = PatronusFati::SERVER_MESSAGE.match(line)
|
|
33
|
+
return unless resp
|
|
34
|
+
|
|
35
|
+
h = Hash[resp.names.zip(resp.captures)]
|
|
36
|
+
|
|
37
|
+
[h['header'], extract_data(h['data'])]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.model_name(hdr)
|
|
41
|
+
hdr.downcase.capitalize.to_sym
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module PatronusFati::MessageProcessor::Alert
|
|
2
|
+
include PatronusFati::MessageProcessor
|
|
3
|
+
|
|
4
|
+
def self.process(obj)
|
|
5
|
+
src_mac = PatronusFati::DataModels::Mac.first_or_create(mac: obj[:source])
|
|
6
|
+
dst_mac = PatronusFati::DataModels::Mac.first_or_create(mac: obj[:dest])
|
|
7
|
+
other_mac = PatronusFati::DataModels::Mac.first_or_create(mac: obj[:other])
|
|
8
|
+
|
|
9
|
+
PatronusFati::DataModels::Alert.first_or_create({created_at: obj.sec, \
|
|
10
|
+
message: obj[:text]}, {created_at: obj.sec, message: obj[:text], \
|
|
11
|
+
src_mac: src_mac, dst_mac: dst_mac, other_mac: other_mac})
|
|
12
|
+
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module PatronusFati::MessageProcessor::Bssid
|
|
2
|
+
include PatronusFati::MessageProcessor
|
|
3
|
+
|
|
4
|
+
def self.process(obj)
|
|
5
|
+
# We don't care about objects that would have expired already...
|
|
6
|
+
return if obj[:lasttime] < (Time.now.to_i - PatronusFati::AP_EXPIRATION)
|
|
7
|
+
|
|
8
|
+
# Ignore probe requests as their BSSID information is useless (the ESSID
|
|
9
|
+
# isn't present and it's coming from a client).
|
|
10
|
+
return unless %w(infrastructure adhoc).include?(obj.type.to_s)
|
|
11
|
+
|
|
12
|
+
ap_info = ap_data(obj.attributes)
|
|
13
|
+
access_point = PatronusFati::DataModels::AccessPoint.first_or_create({bssid: obj.bssid}, ap_info)
|
|
14
|
+
|
|
15
|
+
access_point.update(ap_info)
|
|
16
|
+
access_point.record_signal(obj.signal_dbm)
|
|
17
|
+
access_point.update_frequencies(obj.freqmhz)
|
|
18
|
+
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
protected
|
|
23
|
+
|
|
24
|
+
def self.ap_data(attrs)
|
|
25
|
+
{
|
|
26
|
+
bssid: attrs[:bssid],
|
|
27
|
+
type: attrs[:type],
|
|
28
|
+
channel: attrs[:channel],
|
|
29
|
+
|
|
30
|
+
crypt_packets: attrs[:cryptpackets],
|
|
31
|
+
data_packets: attrs[:datapackets],
|
|
32
|
+
data_size: attrs[:datasize],
|
|
33
|
+
|
|
34
|
+
fragments: attrs[:fragments],
|
|
35
|
+
retries: attrs[:retries],
|
|
36
|
+
|
|
37
|
+
max_seen_rate: attrs[:maxseenrate],
|
|
38
|
+
duplicate_iv_pkts: attrs[:dupeivpackets],
|
|
39
|
+
|
|
40
|
+
range_ip: attrs[:rangeip],
|
|
41
|
+
netmask: attrs[:netmaskip],
|
|
42
|
+
gateway_ip: attrs[:gatewayip],
|
|
43
|
+
|
|
44
|
+
last_seen_at: Time.now.to_i
|
|
45
|
+
}.reject { |_, v| v.nil? }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module PatronusFati::MessageProcessor::Capability
|
|
2
|
+
include PatronusFati::MessageProcessor
|
|
3
|
+
|
|
4
|
+
def self.process(obj)
|
|
5
|
+
# The capability detection for the capability command is broken. It
|
|
6
|
+
# returns the name of the command followed by the capabilities but the
|
|
7
|
+
# result of a request ignores that it also sends back the name of the
|
|
8
|
+
# command. We don't want to mess up our parsing so we work around it by
|
|
9
|
+
# ignoring these messages.
|
|
10
|
+
return if obj.name == 'CAPABILITY'
|
|
11
|
+
return unless PatronusFati::MessageModels.const_defined?(obj.name.downcase.capitalize)
|
|
12
|
+
|
|
13
|
+
target_cap = PatronusFati::MessageModels.const_get(obj.name.downcase.capitalize)
|
|
14
|
+
target_cap.supported_keys = obj.capabilities.split(',').map(&:to_sym)
|
|
15
|
+
|
|
16
|
+
keys_to_enable = target_cap.enabled_keys.map(&:to_s).join(',')
|
|
17
|
+
|
|
18
|
+
# Limit the amount of data kismet gives us to only the interesting stuff
|
|
19
|
+
return unless %w(ERROR PROTOCOLS ALERT BSSID SSID CLIENT CRITFAIL).include?(obj.name)
|
|
20
|
+
|
|
21
|
+
# Return the response to the server
|
|
22
|
+
"ENABLE #{obj.name} #{keys_to_enable}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module PatronusFati::MessageProcessor::Client
|
|
2
|
+
include PatronusFati::MessageProcessor
|
|
3
|
+
|
|
4
|
+
def self.process(obj)
|
|
5
|
+
# We don't care about objects that would have expired already...
|
|
6
|
+
return if obj[:lasttime] < PatronusFati::DataModels::Client.current_expiration_threshold
|
|
7
|
+
|
|
8
|
+
client_info = client_data(obj.attributes)
|
|
9
|
+
|
|
10
|
+
client = PatronusFati::DataModels::Client.first_or_create({bssid: obj[:mac]}, client_info)
|
|
11
|
+
client.update(client_info)
|
|
12
|
+
|
|
13
|
+
client.record_signal(obj.signal_dbm)
|
|
14
|
+
client.update_frequencies(obj.freqmhz)
|
|
15
|
+
|
|
16
|
+
# Don't deal in associations that are outside of our connection expiration
|
|
17
|
+
# time...
|
|
18
|
+
return if obj[:lasttime] <= (Time.now.to_i - PatronusFati::CONNECTION_EXPIRATION)
|
|
19
|
+
|
|
20
|
+
# Handle the associations
|
|
21
|
+
unless obj[:bssid].nil? || obj[:bssid].empty? || obj[:bssid] == obj[:mac]
|
|
22
|
+
return unless (ap = PatronusFati::DataModels::AccessPoint.first(bssid: obj[:bssid]))
|
|
23
|
+
ap.seen!
|
|
24
|
+
|
|
25
|
+
conn = PatronusFati::DataModels::Connection.connected.first_or_create({client: client, access_point: ap})
|
|
26
|
+
conn.seen!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
|
|
34
|
+
def self.client_data(attrs)
|
|
35
|
+
{
|
|
36
|
+
bssid: attrs[:mac],
|
|
37
|
+
channel: attrs[:channel],
|
|
38
|
+
|
|
39
|
+
crypt_packets: attrs[:cryptpackets],
|
|
40
|
+
data_packets: attrs[:datapackets],
|
|
41
|
+
data_size: attrs[:datasize],
|
|
42
|
+
|
|
43
|
+
fragments: attrs[:fragments],
|
|
44
|
+
retries: attrs[:retries],
|
|
45
|
+
|
|
46
|
+
max_seen_rate: attrs[:maxseenrate],
|
|
47
|
+
|
|
48
|
+
ip: attrs[:ip],
|
|
49
|
+
gateway_ip: attrs[:gatewayip],
|
|
50
|
+
dhcp_host: attrs[:dhcphost],
|
|
51
|
+
|
|
52
|
+
last_seen_at: Time.now.to_i
|
|
53
|
+
}.reject { |_, v| v.nil? }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module PatronusFati::MessageProcessor::Ssid
|
|
2
|
+
include PatronusFati::MessageProcessor
|
|
3
|
+
|
|
4
|
+
def self.process(obj)
|
|
5
|
+
# We don't care about objects that would have expired already...
|
|
6
|
+
return if obj[:lasttime] < (Time.now.to_i - PatronusFati::SSID_EXPIRATION)
|
|
7
|
+
|
|
8
|
+
ssid_info = ssid_data(obj.attributes)
|
|
9
|
+
|
|
10
|
+
if %w(beacon probe_response).include?(obj[:type])
|
|
11
|
+
access_point = PatronusFati::DataModels::AccessPoint.first(bssid: obj[:mac])
|
|
12
|
+
return unless access_point # Only happens with a corrupt message
|
|
13
|
+
|
|
14
|
+
ssid = PatronusFati::DataModels::Ssid.first_or_create({access_point: access_point, essid: ssid_info[:essid]}, ssid_info)
|
|
15
|
+
ssid.update(ssid_info)
|
|
16
|
+
access_point.seen!
|
|
17
|
+
elsif obj[:type] == 'probe_request'
|
|
18
|
+
client = PatronusFati::DataModels::Client.first(bssid: obj[:mac])
|
|
19
|
+
|
|
20
|
+
return if client.nil?
|
|
21
|
+
client.seen!
|
|
22
|
+
|
|
23
|
+
return if obj[:ssid].nil? || obj[:ssid].empty?
|
|
24
|
+
client.probes.first_or_create(essid: obj[:ssid])
|
|
25
|
+
else
|
|
26
|
+
# The only thing left is the 'file' type which no one seems to understand
|
|
27
|
+
#puts ('Unknown SSID type (%s): %s' % [obj[:type], obj.inspect])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
protected
|
|
34
|
+
|
|
35
|
+
def self.ssid_data(attrs)
|
|
36
|
+
{
|
|
37
|
+
beacon_info: attrs[:beaconinfo],
|
|
38
|
+
beacon_rate: attrs[:beaconrate],
|
|
39
|
+
|
|
40
|
+
cloaked: attrs[:cloaked],
|
|
41
|
+
crypt_set: attrs[:cryptset].map(&:to_s),
|
|
42
|
+
essid: attrs[:ssid],
|
|
43
|
+
max_rate: attrs[:maxrate],
|
|
44
|
+
|
|
45
|
+
last_seen_at: Time.now.to_i
|
|
46
|
+
}.reject { |_, v| v.nil? }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module PatronusFati
|
|
2
|
+
module MessageProcessor
|
|
3
|
+
extend FactoryBase
|
|
4
|
+
|
|
5
|
+
def self.cleanup_models
|
|
6
|
+
@last_cleanup ||= Time.now.to_i
|
|
7
|
+
|
|
8
|
+
if @last_cleanup < (Time.now.to_i - 10)
|
|
9
|
+
@last_cleanup = Time.now.to_i
|
|
10
|
+
|
|
11
|
+
PatronusFati::DataModels::AccessPoint.inactive.reported_online.each do |ap|
|
|
12
|
+
ap.update(:reported_online => false)
|
|
13
|
+
PatronusFati.event_handler.event(:access_point, :offline, {'bssid' => ap.bssid, 'uptime' => ap.uptime})
|
|
14
|
+
ap.disconnect_clients!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
PatronusFati::DataModels::Client.inactive.reported_online.each do |cli|
|
|
18
|
+
cli.update(:reported_online => false)
|
|
19
|
+
PatronusFati.event_handler.event(:client, :offline, {'bssid' => cli.bssid, 'uptime' => cli.uptime})
|
|
20
|
+
cli.disconnect!
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
PatronusFati::DataModels::Connection.inactive.connected.map(&:disconnect!)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.handle(message_obj)
|
|
28
|
+
result = factory(class_to_name(message_obj), message_obj)
|
|
29
|
+
cleanup_models
|
|
30
|
+
result
|
|
31
|
+
rescue => e
|
|
32
|
+
puts 'Error processing the following message object:'
|
|
33
|
+
puts message_obj.inspect
|
|
34
|
+
puts '%s: %s' % [e.class, e.message]
|
|
35
|
+
puts e.backtrace.join("\n")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.ignored_types
|
|
39
|
+
[:ack, :battery, :bssidsrc, :channel, :clisrc, :gps, :info, :kismet,
|
|
40
|
+
:plugin, :source, :status, :time]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
require 'patronus_fati/message_processor/alert'
|
|
46
|
+
require 'patronus_fati/message_processor/bssid'
|
|
47
|
+
require 'patronus_fati/message_processor/capability'
|
|
48
|
+
require 'patronus_fati/message_processor/client'
|
|
49
|
+
require 'patronus_fati/message_processor/critfail'
|
|
50
|
+
require 'patronus_fati/message_processor/error'
|
|
51
|
+
require 'patronus_fati/message_processor/protocols'
|
|
52
|
+
require 'patronus_fati/message_processor/ssid'
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
STDOUT.sync = true
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
require 'digest'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'openssl'
|
|
7
|
+
require 'socket'
|
|
8
|
+
require 'timeout'
|
|
9
|
+
require 'thread'
|
|
10
|
+
|
|
11
|
+
require 'dm-constraints'
|
|
12
|
+
require 'dm-core'
|
|
13
|
+
require 'dm-migrations'
|
|
14
|
+
require 'dm-observer'
|
|
15
|
+
require 'dm-timestamps'
|
|
16
|
+
require 'dm-validations'
|
|
17
|
+
|
|
18
|
+
require 'louis'
|
|
19
|
+
|
|
20
|
+
require 'patronus_fati/consts'
|
|
21
|
+
require 'patronus_fati/version'
|
|
22
|
+
|
|
23
|
+
require 'patronus_fati/data_mapper/crypt_flags'
|
|
24
|
+
require 'patronus_fati/data_mapper/null_table_prefix'
|
|
25
|
+
|
|
26
|
+
require 'patronus_fati/cap_struct'
|
|
27
|
+
require 'patronus_fati/connection'
|
|
28
|
+
require 'patronus_fati/event_handler'
|
|
29
|
+
require 'patronus_fati/factory_base'
|
|
30
|
+
require 'patronus_fati/message_models'
|
|
31
|
+
require 'patronus_fati/message_parser'
|
|
32
|
+
require 'patronus_fati/message_processor'
|
|
33
|
+
|
|
34
|
+
require 'patronus_fati/data_models/common'
|
|
35
|
+
|
|
36
|
+
require 'patronus_fati/data_models/access_point'
|
|
37
|
+
require 'patronus_fati/data_models/ap_frequency'
|
|
38
|
+
require 'patronus_fati/data_models/ap_signal'
|
|
39
|
+
require 'patronus_fati/data_models/alert'
|
|
40
|
+
require 'patronus_fati/data_models/client'
|
|
41
|
+
require 'patronus_fati/data_models/client_frequency'
|
|
42
|
+
require 'patronus_fati/data_models/client_signal'
|
|
43
|
+
require 'patronus_fati/data_models/connection'
|
|
44
|
+
require 'patronus_fati/data_models/mac'
|
|
45
|
+
require 'patronus_fati/data_models/probe'
|
|
46
|
+
require 'patronus_fati/data_models/ssid'
|
|
47
|
+
|
|
48
|
+
require 'patronus_fati/data_observers/access_point_observer'
|
|
49
|
+
require 'patronus_fati/data_observers/alert_observer'
|
|
50
|
+
require 'patronus_fati/data_observers/client_observer'
|
|
51
|
+
require 'patronus_fati/data_observers/connection_observer'
|
|
52
|
+
require 'patronus_fati/data_observers/ssid_observer'
|
|
53
|
+
|
|
54
|
+
module PatronusFati
|
|
55
|
+
def self.event_handler
|
|
56
|
+
@event_handler ||= PatronusFati::EventHandler.new
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.setup(kismet_server, kismet_port, database_uri)
|
|
60
|
+
#DataMapper::Logger.new('pf-db.log', :debug)
|
|
61
|
+
DataMapper.setup(:default, database_uri)
|
|
62
|
+
DataMapper.repository(:default).adapter.resource_naming_convention = PatronusFati::NullTablePrefix
|
|
63
|
+
DataMapper.finalize
|
|
64
|
+
DataMapper.auto_upgrade!
|
|
65
|
+
|
|
66
|
+
PatronusFati::Connection.new(kismet_server, kismet_port)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'patronus_fati/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |gem|
|
|
8
|
+
gem.name = "patronus_fati"
|
|
9
|
+
gem.version = PatronusFati::VERSION
|
|
10
|
+
gem.authors = [ "Sam Stelfox" ]
|
|
11
|
+
gem.license = "LGPL"
|
|
12
|
+
gem.email = [ "sstelfox@bedroomprogrammers.net" ]
|
|
13
|
+
gem.description = %q{ A ruby implementation of the Kismet client protocol. }
|
|
14
|
+
gem.summary = %q{ A ruby implementation of the Kismet client protocol. }
|
|
15
|
+
gem.homepage = ""
|
|
16
|
+
|
|
17
|
+
gem.files = `git ls-files`.split($/)
|
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
19
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
20
|
+
gem.require_paths = ["lib"]
|
|
21
|
+
|
|
22
|
+
gem.add_dependency 'dm-constraints'
|
|
23
|
+
gem.add_dependency 'dm-core'
|
|
24
|
+
gem.add_dependency 'dm-migrations'
|
|
25
|
+
gem.add_dependency 'dm-observer'
|
|
26
|
+
gem.add_dependency 'dm-sqlite-adapter'
|
|
27
|
+
gem.add_dependency 'dm-timestamps'
|
|
28
|
+
gem.add_dependency 'dm-validations'
|
|
29
|
+
gem.add_dependency 'louis', '~> 2.0'
|
|
30
|
+
|
|
31
|
+
gem.add_development_dependency 'database_cleaner'
|
|
32
|
+
gem.add_development_dependency 'dm-rspec'
|
|
33
|
+
gem.add_development_dependency 'dm-transactions'
|
|
34
|
+
gem.add_development_dependency 'rake'
|
|
35
|
+
gem.add_development_dependency 'rdoc'
|
|
36
|
+
gem.add_development_dependency 'pry'
|
|
37
|
+
gem.add_development_dependency 'redcarpet'
|
|
38
|
+
gem.add_development_dependency 'rspec'
|
|
39
|
+
gem.add_development_dependency 'simplecov'
|
|
40
|
+
gem.add_development_dependency 'yard'
|
|
41
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe 'DataModels::AccessPoint' do
|
|
4
|
+
subject { PatronusFati::DataModels::AccessPoint }
|
|
5
|
+
|
|
6
|
+
let(:unsaved_instance) { subject.new(bssid: '12:34:56:00:00:01', type: 'infrastructure', channel: 1) }
|
|
7
|
+
let(:saved_instance) { unsaved_instance.save }
|
|
8
|
+
|
|
9
|
+
it { expect(subject).to have_property(:bssid) }
|
|
10
|
+
it { expect(subject).to have_property(:type) }
|
|
11
|
+
it { expect(subject).to have_property(:channel) }
|
|
12
|
+
it { expect(subject).to have_property(:last_seen_at) }
|
|
13
|
+
|
|
14
|
+
it { expect(subject).to have_many(:connections) }
|
|
15
|
+
it { expect(subject).to have_many(:ssids) }
|
|
16
|
+
|
|
17
|
+
it { expect(subject).to have_many(:clients).through(:connections) }
|
|
18
|
+
|
|
19
|
+
it { expect(subject).to belong_to(:mac) }
|
|
20
|
+
|
|
21
|
+
it 'should associate to a MAC object before saving' do
|
|
22
|
+
expect(unsaved_instance.mac).to be_nil
|
|
23
|
+
unsaved_instance.save
|
|
24
|
+
expect(unsaved_instance.mac).to be_instance_of(PatronusFati::DataModels::Mac)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe 'DataModels::Alert' do
|
|
4
|
+
subject { PatronusFati::DataModels::Alert }
|
|
5
|
+
|
|
6
|
+
it { expect(subject).to have_property(:created_at) }
|
|
7
|
+
it { expect(subject).to have_property(:message) }
|
|
8
|
+
|
|
9
|
+
it { expect(subject).to belong_to(:src_mac) }
|
|
10
|
+
it { expect(subject).to belong_to(:dst_mac) }
|
|
11
|
+
it { expect(subject).to belong_to(:other_mac) }
|
|
12
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe 'DataModels::Client' do
|
|
4
|
+
subject { PatronusFati::DataModels::Client }
|
|
5
|
+
|
|
6
|
+
let(:unsaved_instance) { subject.new(bssid: '12:34:56:00:00:02') }
|
|
7
|
+
let(:saved_instance) { unsaved_instance.save }
|
|
8
|
+
|
|
9
|
+
it { expect(subject).to have_property(:id) }
|
|
10
|
+
it { expect(subject).to have_property(:bssid) }
|
|
11
|
+
it { expect(subject).to have_property(:last_seen_at) }
|
|
12
|
+
|
|
13
|
+
it { expect(subject).to have_many(:probes) }
|
|
14
|
+
|
|
15
|
+
it { expect(subject).to have_many(:connections) }
|
|
16
|
+
it { expect(subject).to have_many(:access_points).through(:connections) }
|
|
17
|
+
|
|
18
|
+
it { expect(subject).to belong_to(:mac) }
|
|
19
|
+
|
|
20
|
+
it 'should associate to a MAC object before saving' do
|
|
21
|
+
expect(unsaved_instance.mac).to be_nil
|
|
22
|
+
unsaved_instance.save
|
|
23
|
+
expect(unsaved_instance.mac).to be_instance_of(PatronusFati::DataModels::Mac)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe 'DataModels::Connection' do
|
|
4
|
+
subject { PatronusFati::DataModels::Connection }
|
|
5
|
+
|
|
6
|
+
let(:client_model) { PatronusFati::DataModels::Client }
|
|
7
|
+
let(:unsaved_client) { client_model.new(bssid: '11:22:33:00:00:00') }
|
|
8
|
+
|
|
9
|
+
let(:ap_model) { PatronusFati::DataModels::AccessPoint }
|
|
10
|
+
let(:unsaved_ap) { ap_model.new(bssid: '22:33:44:00:00:01', type: 'adhoc', channel: 6) }
|
|
11
|
+
|
|
12
|
+
let(:instance) { subject.new(client: unsaved_client, access_point: unsaved_ap) }
|
|
13
|
+
|
|
14
|
+
it { expect(subject).to have_property(:id) }
|
|
15
|
+
|
|
16
|
+
it { expect(subject).to have_property(:connected_at) }
|
|
17
|
+
it { expect(subject).to have_property(:disconnected_at) }
|
|
18
|
+
|
|
19
|
+
it { expect(subject).to belong_to(:access_point) }
|
|
20
|
+
it { expect(subject).to belong_to(:client) }
|
|
21
|
+
|
|
22
|
+
context '#active?' do
|
|
23
|
+
it 'should be true when a disconnection hasn\'t been registered' do
|
|
24
|
+
inst = instance
|
|
25
|
+
inst.disconnected_at = nil
|
|
26
|
+
|
|
27
|
+
expect(inst).to be_active
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'should be false when a disconnection has been registered' do
|
|
31
|
+
inst = instance
|
|
32
|
+
inst.disconnected_at = Time.now
|
|
33
|
+
|
|
34
|
+
expect(inst).to_not be_active
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context '#disconnect!' do
|
|
39
|
+
it 'should change an active instance to be inactive' do
|
|
40
|
+
inst = instance
|
|
41
|
+
inst.save
|
|
42
|
+
|
|
43
|
+
expect(inst).to be_active
|
|
44
|
+
inst.disconnect!
|
|
45
|
+
expect(inst).to_not be_active
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context '#active scope' do
|
|
50
|
+
it 'should include active connections' do
|
|
51
|
+
inst = instance
|
|
52
|
+
inst.save
|
|
53
|
+
|
|
54
|
+
expect(inst).to be_active
|
|
55
|
+
expect(subject.active).to include(inst)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'should not include inactive connections' do
|
|
59
|
+
inst = instance
|
|
60
|
+
inst.save
|
|
61
|
+
inst.disconnect!
|
|
62
|
+
|
|
63
|
+
expect(inst).to_not be_active
|
|
64
|
+
expect(subject.active).to_not include(inst)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context '#inactive scope' do
|
|
69
|
+
it 'should not include active connections' do
|
|
70
|
+
inst = instance
|
|
71
|
+
inst.save
|
|
72
|
+
|
|
73
|
+
expect(inst).to be_active
|
|
74
|
+
expect(subject.inactive).to_not include(inst)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'should include inactive connections' do
|
|
78
|
+
inst = instance
|
|
79
|
+
inst.save
|
|
80
|
+
inst.disconnect!
|
|
81
|
+
|
|
82
|
+
expect(inst).to_not be_active
|
|
83
|
+
expect(subject.inactive).to include(inst)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe 'DataModels::Mac' do
|
|
4
|
+
subject { PatronusFati::DataModels::Mac }
|
|
5
|
+
|
|
6
|
+
let(:unsaved_instance) { subject.new(mac: '00:12:34:00:00:00') }
|
|
7
|
+
let(:saved_instance) { unsaved_instance.save }
|
|
8
|
+
|
|
9
|
+
it { expect(subject).to have_property(:mac) }
|
|
10
|
+
it { expect(subject).to have_property(:vendor) }
|
|
11
|
+
|
|
12
|
+
it { expect(subject).to have_many(:access_points) }
|
|
13
|
+
it { expect(subject).to have_many(:clients) }
|
|
14
|
+
|
|
15
|
+
it { expect(subject).to have_many(:dst_alerts) }
|
|
16
|
+
it { expect(subject).to have_many(:other_alerts) }
|
|
17
|
+
it { expect(subject).to have_many(:src_alerts) }
|
|
18
|
+
|
|
19
|
+
# This is a tad bit annoying, data mapper generates Ruby warnings about
|
|
20
|
+
# uninitialized instance variables.
|
|
21
|
+
it 'should set the vendor on the MAC object before saving' do
|
|
22
|
+
expect(unsaved_instance.vendor).to be_nil
|
|
23
|
+
unsaved_instance.save
|
|
24
|
+
expect(unsaved_instance.vendor).to_not be_nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe PatronusFati do
|
|
4
|
+
context 'VERSION' do
|
|
5
|
+
it 'should have a version' do
|
|
6
|
+
expect { PatronusFati::VERSION }.to_not raise_error
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'should be properly formatted' do
|
|
10
|
+
expect(PatronusFati::VERSION).to match(/\d+\.\d+\.\d+([a-z])?/)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|