passivedns-client 2.0.6 → 2.1.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 +4 -4
- data/.gitignore +1 -0
- data/README.md +6 -0
- data/Rakefile +8 -0
- data/lib/passivedns/client.rb +14 -7
- data/lib/passivedns/client/cli.rb +34 -6
- data/lib/passivedns/client/passivedb.rb +6 -1
- data/lib/passivedns/client/provider/bfk.rb +102 -0
- data/lib/passivedns/client/provider/circl.rb +111 -0
- data/lib/passivedns/client/provider/cn360.rb +108 -0
- data/lib/passivedns/client/provider/dnsdb.rb +110 -0
- data/lib/passivedns/client/provider/mnemonic.rb +98 -0
- data/lib/passivedns/client/provider/passivetotal.rb +103 -0
- data/lib/passivedns/client/provider/riskiq.rb +130 -0
- data/lib/passivedns/client/provider/tcpiputils.rb +118 -0
- data/lib/passivedns/client/provider/virustotal.rb +105 -0
- data/lib/passivedns/client/state.rb +30 -2
- data/lib/passivedns/client/version.rb +4 -2
- data/test/test_cli.rb +6 -5
- data/test/test_passivedns-client.rb +33 -8
- metadata +11 -10
- data/lib/passivedns/client/providers/bfk.rb +0 -77
- data/lib/passivedns/client/providers/circl.rb +0 -79
- data/lib/passivedns/client/providers/cn360.rb +0 -80
- data/lib/passivedns/client/providers/dnsdb.rb +0 -85
- data/lib/passivedns/client/providers/mnemonic.rb +0 -72
- data/lib/passivedns/client/providers/passivetotal.rb +0 -77
- data/lib/passivedns/client/providers/tcpiputils.rb +0 -92
- data/lib/passivedns/client/providers/virustotal.rb +0 -78
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'openssl'
|
4
|
+
require 'json'
|
5
|
+
require 'digest/md5'
|
6
|
+
|
7
|
+
module PassiveDNS #:nodoc: don't document this
|
8
|
+
# The Provider module contains all the Passive DNS provider client code
|
9
|
+
module Provider
|
10
|
+
# Queries 360.cn's passive DNS database
|
11
|
+
class CN360 < PassiveDB
|
12
|
+
# Sets the modules self-reported name to "360.cn"
|
13
|
+
def self.name
|
14
|
+
"360.cn"
|
15
|
+
end
|
16
|
+
# Sets the configuration section name to "cn360"
|
17
|
+
def self.config_section_name
|
18
|
+
"cn360"
|
19
|
+
end
|
20
|
+
# Sets the command line database argument to "3"
|
21
|
+
def self.option_letter
|
22
|
+
"3"
|
23
|
+
end
|
24
|
+
|
25
|
+
# :debug enables verbose logging to standard output
|
26
|
+
attr_accessor :debug
|
27
|
+
# === Options
|
28
|
+
# * :debug Sets the debug flag for the module
|
29
|
+
# * "API" REQUIRED: http://some.web.address.for.their.api
|
30
|
+
# * "API_ID" REQUIRED: a username that is given when you register
|
31
|
+
# * "API_KEY" REQUIRED: a long and random password of sorts that is used along with the page request to generate a per page API key
|
32
|
+
#
|
33
|
+
# === Example Instantiation
|
34
|
+
#
|
35
|
+
# options = {
|
36
|
+
# :debug => true,
|
37
|
+
# "API" => "http://some.web.address.for.their.api",
|
38
|
+
# "API_ID" => "360user",
|
39
|
+
# "API_KEY" => "360apikey"
|
40
|
+
# }
|
41
|
+
#
|
42
|
+
# PassiveDNS::Provider::CN360.new(options)
|
43
|
+
#
|
44
|
+
def initialize(options={})
|
45
|
+
@debug = options[:debug] || false
|
46
|
+
["API", "API_ID", "API_KEY"].each do |opt|
|
47
|
+
if not options[opt]
|
48
|
+
raise "Field #{opt} is required. See README.md"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
@cp = options
|
52
|
+
end
|
53
|
+
|
54
|
+
# Takes a label (either a domain or an IP address) and returns
|
55
|
+
# an array of PassiveDNS::PDNSResult instances with the answers to the query
|
56
|
+
def lookup(label, limit=10000)
|
57
|
+
table = "rrset"
|
58
|
+
if label =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ or label =~ /^[0-9a-fA-F]+:[0-9a-fA-F:]+[0-9a-fA-F]$/
|
59
|
+
table = "rdata"
|
60
|
+
end
|
61
|
+
limit ||= 10000
|
62
|
+
path = "/api/#{table}/keyword/#{label}/count/#{limit}/"
|
63
|
+
url = @cp["API"]+path
|
64
|
+
url = URI.parse url
|
65
|
+
http = Net::HTTP.new(url.host, url.port)
|
66
|
+
http.use_ssl = (url.scheme == 'https')
|
67
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # I hate doing this
|
68
|
+
http.verify_depth = 5
|
69
|
+
request = Net::HTTP::Get.new(url.path)
|
70
|
+
request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
|
71
|
+
request.add_field('Accept', 'application/json')
|
72
|
+
request.add_field("X-BashTokid", @cp["API_ID"])
|
73
|
+
token = Digest::MD5.hexdigest(path+@cp["API_KEY"])
|
74
|
+
$stderr.puts "DEBUG: cn360 url = #{url} token = #{token}" if @debug
|
75
|
+
request.add_field("X-BashToken", token)
|
76
|
+
t1 = Time.now
|
77
|
+
response = http.request(request)
|
78
|
+
t2 = Time.now
|
79
|
+
recs = parse_json(response.body, label, t2-t1)
|
80
|
+
if limit
|
81
|
+
recs[0,limit]
|
82
|
+
else
|
83
|
+
recs
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# parses the response of 360.cn's JSON reply to generate an array of PDNSResult
|
89
|
+
def parse_json(page,query,response_time=0)
|
90
|
+
res = []
|
91
|
+
# need to remove the json_class tag or the parser will crap itself trying to find a class to align it to
|
92
|
+
data = JSON.parse(page)
|
93
|
+
data.each do |row|
|
94
|
+
time_first = (row["time_first"]) ? Time.at(row["time_first"].to_i) : nil
|
95
|
+
time_last = (row["time_last"]) ? Time.at(row["time_last"].to_i) : nil
|
96
|
+
count = row["count"] || 0
|
97
|
+
res << PDNSResult.new(self.class.name, response_time, row["rrname"], row["rdata"], row["rrtype"], time_first, time_last, count)
|
98
|
+
end
|
99
|
+
res
|
100
|
+
rescue Exception => e
|
101
|
+
$stderr.puts "#{self.class.name} Exception: #{e}"
|
102
|
+
raise e
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# DESCRIPTION: this is a module for pdns.rb, primarily used by pdnstool.rb, to query the Farsight Security passive DNS database
|
2
|
+
# details on the API are at https://api.dnsdb.info/
|
3
|
+
# to request an API key, please email dnsdb-api at farsightsecurity dot com.
|
4
|
+
require 'net/http'
|
5
|
+
require 'net/https'
|
6
|
+
|
7
|
+
module PassiveDNS #:nodoc: don't document this
|
8
|
+
# The Provider module contains all the Passive DNS provider client code
|
9
|
+
module Provider
|
10
|
+
# Queries FarSight's passive DNS database
|
11
|
+
class DNSDB < PassiveDB
|
12
|
+
# Sets the modules self-reported name to "DNSDB"
|
13
|
+
def self.name
|
14
|
+
"DNSDB"
|
15
|
+
end
|
16
|
+
# Sets the configuration section name to "dnsdb"
|
17
|
+
def self.config_section_name
|
18
|
+
"dnsdb"
|
19
|
+
end
|
20
|
+
# Sets the command line database argument to "d"
|
21
|
+
def self.option_letter
|
22
|
+
"d"
|
23
|
+
end
|
24
|
+
|
25
|
+
# :debug enables verbose logging to standard output
|
26
|
+
attr_accessor :debug
|
27
|
+
# === Options
|
28
|
+
# * :debug Sets the debug flag for the module
|
29
|
+
# * "APIKEY" REQUIRED: The API key associated with DNSDB
|
30
|
+
# * "URL" Alternate url for testing. Defaults to "https://api.dnsdb.info/lookup"
|
31
|
+
#
|
32
|
+
# === Example Instantiation
|
33
|
+
#
|
34
|
+
# options = {
|
35
|
+
# :debug => true,
|
36
|
+
# "APIKEY" => "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
37
|
+
# "URL" => "https://api.dnsdb.info/lookup"
|
38
|
+
# }
|
39
|
+
#
|
40
|
+
# PassiveDNS::Provider::DNSDB.new(options)
|
41
|
+
#
|
42
|
+
def initialize(options={})
|
43
|
+
@debug = options[:debug] || false
|
44
|
+
@key = options["APIKEY"] || raise("APIKEY option required for #{self.class}")
|
45
|
+
@base = options["URL"] || "https://api.dnsdb.info/lookup"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Takes a label (either a domain or an IP address) and returns
|
49
|
+
# an array of PassiveDNS::PDNSResult instances with the answers to the query
|
50
|
+
def lookup(label, limit=nil)
|
51
|
+
$stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
|
52
|
+
Timeout::timeout(240) {
|
53
|
+
url = nil
|
54
|
+
if label =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$/
|
55
|
+
label = label.gsub(/\//,',')
|
56
|
+
url = "#{@base}/rdata/ip/#{label}"
|
57
|
+
else
|
58
|
+
url = "#{@base}/rrset/name/#{label}"
|
59
|
+
end
|
60
|
+
url = URI.parse url
|
61
|
+
http = Net::HTTP.new(url.host, url.port)
|
62
|
+
http.use_ssl = (url.scheme == 'https')
|
63
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
64
|
+
http.verify_depth = 5
|
65
|
+
path = url.path
|
66
|
+
if limit
|
67
|
+
path << "?limit=#{limit}"
|
68
|
+
end
|
69
|
+
request = Net::HTTP::Get.new(path)
|
70
|
+
request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
|
71
|
+
request.add_field("X-API-Key", @key)
|
72
|
+
request.add_field("Accept", "application/json")
|
73
|
+
t1 = Time.now
|
74
|
+
response = http.request(request)
|
75
|
+
t2 = Time.now
|
76
|
+
$stderr.puts response.body if @debug
|
77
|
+
parse_json(response.body,t2-t1)
|
78
|
+
}
|
79
|
+
rescue Timeout::Error => e
|
80
|
+
$stderr.puts "#{self.class.name} lookup timed out: #{label}"
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# parses the response of DNSDB's JSON reply to generate an array of PDNSResult
|
86
|
+
def parse_json(page,response_time)
|
87
|
+
res = []
|
88
|
+
raise "Error: unable to parse request" if page =~ /Error: unable to parse request/
|
89
|
+
# need to remove the json_class tag or the parser will crap itself trying to find a class to align it to
|
90
|
+
rows = page.split(/\n/)
|
91
|
+
rows.each do |row|
|
92
|
+
record = JSON.parse(row)
|
93
|
+
record['rdata'] = [record['rdata']] if record['rdata'].class == String
|
94
|
+
record['rdata'].each do |rdata|
|
95
|
+
if record['time_first']
|
96
|
+
res << PDNSResult.new(self.class.name,response_time,record['rrname'],rdata,record['rrtype'],0,Time.at(record['time_first'].to_i).utc.strftime("%Y-%m-%dT%H:%M:%SZ"),Time.at(record['time_last'].to_i).utc.strftime("%Y-%m-%dT%H:%M:%SZ"),record['count'])
|
97
|
+
else
|
98
|
+
res << PDNSResult.new(self.class.name,response_time,record['rrname'],rdata,record['rrtype'])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
res
|
103
|
+
rescue Exception => e
|
104
|
+
$stderr.puts "#{self.class.name} Exception: #{e}"
|
105
|
+
$stderr.puts page
|
106
|
+
raise e
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# DESCRIPTION: Module to query Mnemonic's passive DNS repository
|
2
|
+
# CONTRIBUTOR: Drew Hunt (pinowudi@yahoo.com)
|
3
|
+
require 'net/http'
|
4
|
+
require 'net/https'
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
module PassiveDNS #:nodoc: don't document this
|
8
|
+
# The Provider module contains all the Passive DNS provider client code
|
9
|
+
module Provider
|
10
|
+
# Queries Mnemonic's passive DNS database
|
11
|
+
class Mnemonic < PassiveDB
|
12
|
+
# Sets the modules self-reported name to "Mnemonic"
|
13
|
+
def self.name
|
14
|
+
"Mnemonic"
|
15
|
+
end
|
16
|
+
# Sets the configuration section name to "mnemonic"
|
17
|
+
def self.config_section_name
|
18
|
+
"mnemonic"
|
19
|
+
end
|
20
|
+
# Sets the command line database argument to "m"
|
21
|
+
def self.option_letter
|
22
|
+
"m"
|
23
|
+
end
|
24
|
+
|
25
|
+
# :debug enables verbose logging to standard output
|
26
|
+
attr_accessor :debug
|
27
|
+
# === Options
|
28
|
+
# * :debug Sets the debug flag for the module
|
29
|
+
# * "APIKEY" REQUIRED: The API key associated with Mnemonic
|
30
|
+
# * "URL" Alternate url for testing. Defaults to "https://passivedns.mnemonic.no/api1/?apikey="
|
31
|
+
#
|
32
|
+
# === Example Instantiation
|
33
|
+
#
|
34
|
+
# options = {
|
35
|
+
# :debug => true,
|
36
|
+
# "APIKEY" => "01234567890abcdef01234567890abcdef012345",
|
37
|
+
# "URL" => "https://passivedns.mnemonic.no/api1/?apikey="
|
38
|
+
# }
|
39
|
+
#
|
40
|
+
# PassiveDNS::Provider::Mnemonic.new(options)
|
41
|
+
#
|
42
|
+
def initialize(options={})
|
43
|
+
@debug = options[:debug] || false
|
44
|
+
@apikey = options["APIKEY"] || raise("#{self.class.name} requires an APIKEY")
|
45
|
+
@url = options["URL"] || "https://passivedns.mnemonic.no/api1/?apikey="
|
46
|
+
end
|
47
|
+
|
48
|
+
# Takes a label (either a domain or an IP address) and returns
|
49
|
+
# an array of PassiveDNS::PDNSResult instances with the answers to the query
|
50
|
+
def lookup(label, limit=nil)
|
51
|
+
$stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
|
52
|
+
Timeout::timeout(240) {
|
53
|
+
url = "#{@url}#{@apikey}&query=#{label}&method=exact"
|
54
|
+
$stderr.puts "DEBUG: #{self.class.name} url = #{url}" if @debug
|
55
|
+
url = URI.parse url
|
56
|
+
http = Net::HTTP.new(url.host, url.port)
|
57
|
+
http.use_ssl = (url.scheme == 'https')
|
58
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
59
|
+
http.verify_depth = 5
|
60
|
+
request = Net::HTTP::Get.new(url.path+"?"+url.query)
|
61
|
+
request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
|
62
|
+
t1 = Time.now
|
63
|
+
response = http.request(request)
|
64
|
+
t2 = Time.now
|
65
|
+
recs = parse_json(response.body, label, t2-t1)
|
66
|
+
if limit
|
67
|
+
recs[0,limit]
|
68
|
+
else
|
69
|
+
recs
|
70
|
+
end
|
71
|
+
}
|
72
|
+
rescue Timeout::Error => e
|
73
|
+
$stderr.puts "#{self.class.name} lookup timed out: #{label}"
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# parses the response of mnemonic's JSON reply to generate an array of PDNSResult
|
79
|
+
def parse_json(page,query,response_time=0)
|
80
|
+
res = []
|
81
|
+
# need to remove the json_class tag or the parser will crap itself trying to find a class to align it to
|
82
|
+
data = JSON.parse(page)
|
83
|
+
if data['result']
|
84
|
+
data['result'].each do |row|
|
85
|
+
if row['query']
|
86
|
+
res << PDNSResult.new(self.class.name,response_time,row['query'],row['answer'],row['type'].upcase,row['ttl'],row['first'],row['last'])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
res
|
91
|
+
rescue Exception => e
|
92
|
+
$stderr.puts "#{self.class.name} Exception: #{e}"
|
93
|
+
raise e
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# DESCRIPTION: Module to query PassiveTotal's passive DNS repository
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'net/https'
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
module PassiveDNS #:nodoc: don't document this
|
8
|
+
# The Provider module contains all the Passive DNS provider client code
|
9
|
+
module Provider
|
10
|
+
# Queries PassiveTotal's passive DNS database
|
11
|
+
class PassiveTotal < PassiveDB
|
12
|
+
# Sets the modules self-reported name to "PassiveTotal"
|
13
|
+
def self.name
|
14
|
+
"PassiveTotal"
|
15
|
+
end
|
16
|
+
# Sets the configuration section name to "passivetotal"
|
17
|
+
def self.config_section_name
|
18
|
+
"passivetotal"
|
19
|
+
end
|
20
|
+
# Sets the command line database argument to "p"
|
21
|
+
def self.option_letter
|
22
|
+
"p"
|
23
|
+
end
|
24
|
+
|
25
|
+
# :debug enables verbose logging to standard output
|
26
|
+
attr_accessor :debug
|
27
|
+
# === Options
|
28
|
+
# * :debug Sets the debug flag for the module
|
29
|
+
# * "APIKEY" REQUIRED: The API key associated with PassiveTotal
|
30
|
+
# * "URL" Alternate url for testing. Defaults to "https://www.passivetotal.org/api/passive"
|
31
|
+
#
|
32
|
+
# === Example Instantiation
|
33
|
+
#
|
34
|
+
# options = {
|
35
|
+
# :debug => true,
|
36
|
+
# "APIKEY" => "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
37
|
+
# "URL" => "https://www.passivetotal.org/api/passive"
|
38
|
+
# }
|
39
|
+
#
|
40
|
+
# PassiveDNS::Provider::PassiveTotal.new(options)
|
41
|
+
#
|
42
|
+
def initialize(options={})
|
43
|
+
@debug = options[:debug] || false
|
44
|
+
@apikey = options["APIKEY"] || raise("#{self.class.name} requires an APIKEY")
|
45
|
+
@url = options["URL"] || "https://www.passivetotal.org/api/passive"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Takes a label (either a domain or an IP address) and returns
|
49
|
+
# an array of PassiveDNS::PDNSResult instances with the answers to the query
|
50
|
+
def lookup(label, limit=nil)
|
51
|
+
$stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
|
52
|
+
Timeout::timeout(240) {
|
53
|
+
url = @url
|
54
|
+
$stderr.puts "DEBUG: #{self.class.name} url = #{url}" if @debug
|
55
|
+
url = URI.parse url
|
56
|
+
http = Net::HTTP.new(url.host, url.port)
|
57
|
+
http.use_ssl = (url.scheme == 'https')
|
58
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
59
|
+
http.verify_depth = 5
|
60
|
+
request = Net::HTTP::Post.new(url.request_uri)
|
61
|
+
request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
|
62
|
+
request.set_form_data({"apikey" => @apikey, "value" => label})
|
63
|
+
t1 = Time.now
|
64
|
+
response = http.request(request)
|
65
|
+
t2 = Time.now
|
66
|
+
recs = parse_json(response.body, label, t2-t1)
|
67
|
+
if limit
|
68
|
+
recs[0,limit]
|
69
|
+
else
|
70
|
+
recs
|
71
|
+
end
|
72
|
+
}
|
73
|
+
rescue Timeout::Error => e
|
74
|
+
$stderr.puts "#{self.class.name} lookup timed out: #{label}"
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# parses the response of passivetotals's JSON reply to generate an array of PDNSResult
|
80
|
+
def parse_json(page,query,response_time=0)
|
81
|
+
res = []
|
82
|
+
# need to remove the json_class tag or the parser will crap itself trying to find a class to align it to
|
83
|
+
data = JSON.parse(page)
|
84
|
+
if data['results']
|
85
|
+
query = data['results']['value']
|
86
|
+
data['results']['resolutions'].each do |row|
|
87
|
+
first_seen = row['firstSeen']
|
88
|
+
last_seen = row['lastSeen']
|
89
|
+
value = row['value']
|
90
|
+
source = row['source'].join(",")
|
91
|
+
res << PDNSResult.new(self.class.name+"/"+source,response_time,
|
92
|
+
query, value, "A", 0, first_seen, last_seen)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
res
|
96
|
+
rescue Exception => e
|
97
|
+
$stderr.puts "#{self.class.name} Exception: #{e}"
|
98
|
+
raise e
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# DESCRIPTION: Module to query PassiveTotal's passive DNS repository
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'net/https'
|
5
|
+
require 'openssl'
|
6
|
+
#require 'pp'
|
7
|
+
|
8
|
+
module PassiveDNS #:nodoc: don't document this
|
9
|
+
# The Provider module contains all the Passive DNS provider client code
|
10
|
+
module Provider
|
11
|
+
# Queries RiskIQ's passive DNS database
|
12
|
+
class RiskIQ < PassiveDB
|
13
|
+
# Sets the modules self-reported name to "RiskIQ"
|
14
|
+
def self.name
|
15
|
+
"RiskIQ"
|
16
|
+
end
|
17
|
+
# Sets the configuration section name to "riskiq"
|
18
|
+
def self.config_section_name
|
19
|
+
"riskiq"
|
20
|
+
end
|
21
|
+
# Sets the command line database argument to "r"
|
22
|
+
def self.option_letter
|
23
|
+
"r"
|
24
|
+
end
|
25
|
+
|
26
|
+
# :debug enables verbose logging to standard output
|
27
|
+
attr_accessor :debug
|
28
|
+
# === Options
|
29
|
+
# * :debug Sets the debug flag for the module
|
30
|
+
# * "API_TOKEN" REQUIRED: User name associated with your RiskIQ account
|
31
|
+
# * "API_PRIVATE_KEY" REQUIRED: Password associated with your RiskIQ account
|
32
|
+
# * "API_SERVER" Alternate server for testing. Defaults to "ws.riskiq.net"
|
33
|
+
# * "API_VERSION" Alternate version of the API to test. Defaults to "V1"
|
34
|
+
#
|
35
|
+
# === Example Instantiation
|
36
|
+
#
|
37
|
+
# options = {
|
38
|
+
# :debug => true,
|
39
|
+
# "API_TOKEN" => "riskiq_token",
|
40
|
+
# "API_PRIVATE_KEY" => "riskiq_private_key",
|
41
|
+
# "API_SERVER" => "ws.riskiq.net",
|
42
|
+
# "API_VERSION" => "v1"
|
43
|
+
# }
|
44
|
+
#
|
45
|
+
# PassiveDNS::Provider::RiskIQ.new(options)
|
46
|
+
#
|
47
|
+
def initialize(options={})
|
48
|
+
@debug = options[:debug] || false
|
49
|
+
@token = options["API_TOKEN"] || raise("#{self.class.name} requires an API_TOKEN")
|
50
|
+
@privkey = options["API_PRIVATE_KEY"] || raise("#{self.class.name} requires an API_PRIVATE_KEY")
|
51
|
+
@server = options["API_SERVER"] || "ws.riskiq.net"
|
52
|
+
@version = options["API_VERSION"] || "v1"
|
53
|
+
@url = "https://#{@server}/#{@version}"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Takes a label (either a domain or an IP address) and returns
|
57
|
+
# an array of PassiveDNS::PDNSResult instances with the answers to the query
|
58
|
+
def lookup(label, limit=nil)
|
59
|
+
$stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
|
60
|
+
Timeout::timeout(240) {
|
61
|
+
url = nil
|
62
|
+
params = {"rrType" => "", "maxResults" => limit || 1000}
|
63
|
+
|
64
|
+
if label =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
|
65
|
+
url = @url+"/dns/data"
|
66
|
+
params["ip"] = label
|
67
|
+
else
|
68
|
+
url = @url+"/dns/name"
|
69
|
+
params["name"] = label
|
70
|
+
end
|
71
|
+
url << "?"
|
72
|
+
params.each do |k,v|
|
73
|
+
url << "#{k}=#{v}&"
|
74
|
+
end
|
75
|
+
url.gsub!(/\&$/,"")
|
76
|
+
|
77
|
+
$stderr.puts "DEBUG: #{self.class.name} url = #{url}" if @debug
|
78
|
+
url = URI.parse url
|
79
|
+
http = Net::HTTP.new(url.host, url.port)
|
80
|
+
http.use_ssl = (url.scheme == 'https')
|
81
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
82
|
+
http.verify_depth = 5
|
83
|
+
request = Net::HTTP::Get.new(url.request_uri)
|
84
|
+
request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
|
85
|
+
request.add_field('Accept', 'Application/JSON')
|
86
|
+
request.add_field('Content-Type', 'Application/JSON')
|
87
|
+
request.basic_auth(@token, @privkey)
|
88
|
+
t1 = Time.now
|
89
|
+
response = http.request(request)
|
90
|
+
t2 = Time.now
|
91
|
+
recs = parse_json(response.body, label, t2-t1)
|
92
|
+
if limit
|
93
|
+
recs[0,limit]
|
94
|
+
else
|
95
|
+
recs
|
96
|
+
end
|
97
|
+
}
|
98
|
+
rescue Timeout::Error => e
|
99
|
+
$stderr.puts "#{self.class.name} lookup timed out: #{label}"
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
# parses the response of riskiq's JSON reply to generate an array of PDNSResult
|
105
|
+
def parse_json(page,query,response_time=0)
|
106
|
+
#pp page
|
107
|
+
res = []
|
108
|
+
# need to remove the json_class tag or the parser will crap itself trying to find a class to align it to
|
109
|
+
data = JSON.parse(page)
|
110
|
+
if data['records']
|
111
|
+
data['records'].each do |record|
|
112
|
+
name = record['name']
|
113
|
+
type = record['rrtype']
|
114
|
+
last_seen = record['lastSeen']
|
115
|
+
first_seen = record['firstSeen']
|
116
|
+
record['data'].each do |datum|
|
117
|
+
res << PDNSResult.new(self.class.name,response_time,
|
118
|
+
name, datum, type, 0, first_seen, last_seen)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
res
|
123
|
+
rescue Exception => e
|
124
|
+
$stderr.puts "#{self.class.name} Exception: #{e}"
|
125
|
+
raise e
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|