patronus_fati 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|