passivedns-client 2.1.6 → 2.1.12
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 +5 -5
- data/.gitignore +0 -0
- data/Gemfile +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +3 -0
- data/lib/passivedns/client.rb +77 -37
- data/lib/passivedns/client/cli.rb +149 -141
- data/lib/passivedns/client/passivedb.rb +0 -0
- data/lib/passivedns/client/provider/bfk.rb +56 -52
- data/lib/passivedns/client/provider/circl.rb +55 -39
- data/lib/passivedns/client/provider/cn360.rb +44 -33
- data/lib/passivedns/client/provider/dnsdb.rb +57 -56
- data/lib/passivedns/client/provider/mnemonic.rb +63 -55
- data/lib/passivedns/client/provider/passivetotal.rb +48 -43
- data/lib/passivedns/client/provider/riskiq.rb +59 -43
- data/lib/passivedns/client/provider/tcpiputils.rb +20 -19
- data/lib/passivedns/client/provider/virustotal.rb +60 -46
- data/lib/passivedns/client/state.rb +237 -236
- data/lib/passivedns/client/version.rb +1 -1
- data/passivedns-client.gemspec +6 -6
- data/test/helper.rb +0 -0
- data/test/test_cli.rb +34 -5
- data/test/test_passivedns-client.rb +153 -146
- metadata +23 -24
File without changes
|
@@ -5,7 +5,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
5
5
|
module Provider
|
6
6
|
|
7
7
|
# Queries BFK.de's passive DNS database
|
8
|
-
|
8
|
+
class BFK < PassiveDB
|
9
9
|
# Sets the modules self-reported name to "BFK.de"
|
10
10
|
def self.name
|
11
11
|
"BFK.de"
|
@@ -34,72 +34,76 @@ module PassiveDNS #:nodoc: don't document this
|
|
34
34
|
#
|
35
35
|
# PassiveDNS::Provider::BFK.new(options)
|
36
36
|
#
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
def initialize(options={})
|
38
|
+
@debug = options[:debug] || false
|
39
|
+
@timeout = options[:timeout] || 20
|
40
40
|
@base = options["URL"] || "http://www.bfk.de/bfk_dnslogger.html?query="
|
41
|
-
|
41
|
+
raise "Due to the EU GDPR policy, this service has been shut down until further notice."
|
42
|
+
end
|
42
43
|
|
43
44
|
# Takes a label (either a domain or an IP address) and returns
|
44
45
|
# an array of PassiveDNS::PDNSResult instances with the answers to the query
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
46
|
+
def lookup(label, limit=nil)
|
47
|
+
$stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
|
48
|
+
Timeout::timeout(@timeout) {
|
49
|
+
t1 = Time.now
|
50
|
+
open(
|
51
|
+
@base+label,
|
52
|
+
"User-Agent" => "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}"
|
53
|
+
) do |f|
|
54
|
+
t2 = Time.now
|
55
|
+
recs = parse(f.read,t2-t1)
|
55
56
|
if limit
|
56
57
|
recs[0,limit]
|
57
58
|
else
|
58
59
|
recs
|
59
60
|
end
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
61
|
+
end
|
62
|
+
}
|
63
|
+
rescue Timeout::Error
|
64
|
+
$stderr.puts "#{self.class.name} lookup timed out: #{label}"
|
65
|
+
end
|
65
66
|
|
66
67
|
private
|
67
68
|
|
68
69
|
# parses the webpage returned by BFK to generate an array of PDNSResult
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
70
|
+
def parse(page,response_time)
|
71
|
+
line = page.unpack('C*').pack('U*').split(/<table/).grep(/ id=\"logger\"/)
|
72
|
+
return [] unless line.length > 0
|
73
|
+
line = line[0].gsub(/[\t\n]/,'').gsub(/<\/table.*/,'')
|
74
|
+
rows = line.split(/<tr.*?>/)
|
75
|
+
res = []
|
76
|
+
rows.collect do |row|
|
77
|
+
r = row.split(/<td>/).map{|x| x.gsub(/<.*?>/,'').gsub(/\&.*?;/,'')}[1,1000]
|
78
|
+
if r and r[0] =~ /\w/
|
79
|
+
# BFK includes the MX weight in the answer response. First, find the MX records,
|
80
|
+
# then dump the weight to present a consistent record name to the collecting
|
81
|
+
# array. Otherwise the other repositories will present the same answer and
|
82
|
+
# your results will become cluttered with duplicates.
|
83
|
+
if r[1] == "MX" then
|
84
|
+
# MX lines look like "5 mx.domain.tld", so split on the space and assign r[2] (:answer) to the latter part.
|
85
|
+
#s = r[2].split(/\w/).map{|x| x}[1,1000]
|
86
|
+
# r[2] = s[1]
|
87
|
+
r[2] =~ /[0-9]+?\s(.+)/
|
88
|
+
s=$1
|
89
|
+
#puts "DEBUG: == BFK: MX Parsing Strip: Answer: " + r[2] + " : mod: " + s if @debug
|
90
|
+
r[2] = s
|
91
|
+
|
92
|
+
######### FIX BLANKS FOR MX
|
93
|
+
|
94
|
+
end
|
91
95
|
query = r[0]
|
92
96
|
answer = r[2]
|
93
97
|
rrtype = r[1]
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
98
|
+
res << PDNSResult.new(self.class.name,response_time,query,answer,rrtype,'white')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
res
|
102
|
+
rescue Exception => e
|
103
|
+
$stderr.puts "#{self.class.name} Exception: #{e}"
|
104
|
+
raise e
|
105
|
+
end
|
102
106
|
|
103
|
-
|
107
|
+
end
|
104
108
|
end
|
105
|
-
end
|
109
|
+
end
|
@@ -9,7 +9,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
9
9
|
module Provider
|
10
10
|
# Queries CIRCL.LU's passive DNS database
|
11
11
|
# Circl is aliased by CIRCL
|
12
|
-
|
12
|
+
class Circl < PassiveDB
|
13
13
|
# Sets the modules self-reported name to "CIRCL"
|
14
14
|
def self.name
|
15
15
|
"CIRCL"
|
@@ -45,68 +45,84 @@ module PassiveDNS #:nodoc: don't document this
|
|
45
45
|
#
|
46
46
|
# PassiveDNS::Provider::CIRCL.new(options)
|
47
47
|
#
|
48
|
-
|
48
|
+
def initialize(options={})
|
49
49
|
@debug = options[:debug] || false
|
50
|
+
@timeout = options[:timeout] || 20
|
50
51
|
@username = options["USERNAME"]
|
51
52
|
@password = options["PASSWORD"]
|
52
53
|
@auth_token = options["AUTH_TOKEN"]
|
53
54
|
@url = options["URL"] || "https://www.circl.lu/pdns/query"
|
54
|
-
|
55
|
+
end
|
55
56
|
|
56
57
|
# Takes a label (either a domain or an IP address) and returns
|
57
58
|
# an array of PassiveDNS::PDNSResult instances with the answers to the query
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
59
|
+
def lookup(label, limit=nil)
|
60
|
+
$stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
|
61
|
+
recs = []
|
62
|
+
Timeout::timeout(@timeout) {
|
63
|
+
url = @url+"/"+label
|
64
|
+
$stderr.puts "DEBUG: #{self.class.name} url = #{url}" if @debug
|
65
|
+
begin
|
66
|
+
url = URI.parse url
|
67
|
+
rescue URI::InvalidURIError
|
68
|
+
$stderr.puts "ERROR: Invalid address: #{url}"
|
69
|
+
return recs
|
70
|
+
end
|
71
|
+
http = Net::HTTP.new(url.host, url.port)
|
72
|
+
http.use_ssl = (url.scheme == 'https')
|
73
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
74
|
+
http.verify_depth = 5
|
75
|
+
request = Net::HTTP::Get.new(url.request_uri)
|
76
|
+
request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
|
70
77
|
if @username
|
71
78
|
request.basic_auth(@username, @password)
|
72
79
|
end
|
73
80
|
if @auth_token
|
74
81
|
request.add_field("Authorization", @auth_token)
|
75
82
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
83
|
+
t1 = Time.now
|
84
|
+
0.upto(9) do
|
85
|
+
response = http.request(request)
|
86
|
+
body = response.body
|
87
|
+
if body == "Rate Limit Exceeded"
|
88
|
+
$stderr.puts "DEBUG: Rate Limit Exceeded. Retrying #{label}" if @debug
|
89
|
+
else
|
90
|
+
t2 = Time.now
|
91
|
+
recs = parse_json(response.body, label, t2-t1)
|
92
|
+
break
|
93
|
+
end
|
94
|
+
end
|
95
|
+
if limit
|
96
|
+
recs[0,limit]
|
97
|
+
else
|
98
|
+
recs
|
99
|
+
end
|
100
|
+
}
|
101
|
+
rescue Timeout::Error
|
102
|
+
$stderr.puts "#{self.class.name} lookup timed out: #{label}"
|
103
|
+
recs
|
104
|
+
end
|
89
105
|
|
90
106
|
private
|
91
107
|
|
92
108
|
# parses the response of circl's JSON reply to generate an array of PDNSResult
|
93
|
-
|
94
|
-
|
109
|
+
def parse_json(page,query,response_time=0)
|
110
|
+
res = []
|
95
111
|
page.split(/\n/).each do |line|
|
96
112
|
row = JSON.parse(line)
|
97
113
|
firstseen = Time.at(row['time_first'].to_i)
|
98
114
|
lastseen = Time.at(row['time_last'].to_i)
|
99
|
-
|
115
|
+
res << PDNSResult.new(self.class.name,response_time,
|
100
116
|
row['rrname'], row['rdata'], row['rrtype'], 0,
|
101
|
-
firstseen, lastseen, row['count'])
|
117
|
+
firstseen, lastseen, row['count'], 'yellow')
|
102
118
|
end
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
119
|
+
res
|
120
|
+
rescue Exception => e
|
121
|
+
$stderr.puts "#{self.class.name} Exception: #{e}"
|
122
|
+
raise e
|
123
|
+
end
|
108
124
|
|
109
|
-
|
125
|
+
end
|
110
126
|
CIRCL = PassiveDNS::Provider::Circl
|
111
127
|
end
|
112
|
-
end
|
128
|
+
end
|
@@ -3,6 +3,7 @@ require 'net/https'
|
|
3
3
|
require 'openssl'
|
4
4
|
require 'json'
|
5
5
|
require 'digest/md5'
|
6
|
+
require 'pp'
|
6
7
|
|
7
8
|
module PassiveDNS #:nodoc: don't document this
|
8
9
|
# The Provider module contains all the Passive DNS provider client code
|
@@ -43,6 +44,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
43
44
|
#
|
44
45
|
def initialize(options={})
|
45
46
|
@debug = options[:debug] || false
|
47
|
+
@timeout = options[:timeout] || 20
|
46
48
|
["API", "API_ID", "API_KEY"].each do |opt|
|
47
49
|
if not options[opt]
|
48
50
|
raise "Field #{opt} is required. See README.md"
|
@@ -60,52 +62,61 @@ module PassiveDNS #:nodoc: don't document this
|
|
60
62
|
end
|
61
63
|
limit ||= 10000
|
62
64
|
path = "/api/#{table}/keyword/#{label}/count/#{limit}/"
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
65
|
+
Timeout::timeout(@timeout) {
|
66
|
+
url = @cp["API"]+path
|
67
|
+
url = URI.parse url
|
68
|
+
http = Net::HTTP.new(url.host, url.port)
|
69
|
+
http.use_ssl = (url.scheme == 'https')
|
70
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # I hate doing this
|
71
|
+
http.verify_depth = 5
|
72
|
+
request = Net::HTTP::Get.new(url.path)
|
73
|
+
request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
|
74
|
+
request.add_field('Accept', 'application/json')
|
75
|
+
request.add_field("X-BashTokid", @cp["API_ID"])
|
76
|
+
token = Digest::MD5.hexdigest(path+@cp["API_KEY"])
|
77
|
+
$stderr.puts "DEBUG: cn360 url = #{url} token = #{token}" if @debug
|
78
|
+
request.add_field("X-BashToken", token)
|
79
|
+
t1 = Time.now
|
80
|
+
response = http.request(request)
|
81
|
+
t2 = Time.now
|
82
|
+
recs = parse_json(response.body, label, t2-t1)
|
83
|
+
if limit
|
84
|
+
recs[0,limit]
|
85
|
+
else
|
86
|
+
recs
|
87
|
+
end
|
88
|
+
}
|
89
|
+
rescue Timeout::Error
|
90
|
+
$stderr.puts "#{self.class.name} lookup timed out: #{label}"
|
91
|
+
recs
|
85
92
|
end
|
86
93
|
|
87
94
|
private
|
88
95
|
|
89
96
|
# parses the response of 360.cn's JSON reply to generate an array of PDNSResult
|
90
97
|
def parse_json(page,query,response_time=0)
|
91
|
-
|
92
|
-
|
98
|
+
res = []
|
99
|
+
data = JSON.parse(page)
|
100
|
+
if data.class == Hash and data['err']
|
101
|
+
raise "#{self.class.name} Error: #{data['err']}"
|
102
|
+
end
|
93
103
|
data.each do |row|
|
94
|
-
time_first = (row["time_first"]) ? Time.at(row["time_first"]
|
95
|
-
time_last = (row["time_last"]) ? Time.at(row["time_last"]
|
104
|
+
time_first = (row["time_first"]) ? Time.at(row["time_first"]) : nil
|
105
|
+
time_last = (row["time_last"]) ? Time.at(row["time_last"]) : nil
|
96
106
|
count = row["count"] || 0
|
97
107
|
query = row["rrname"]
|
98
108
|
answers = row["rdata"].gsub(/;$/,'').split(/;/)
|
99
109
|
rrtype = row["rrtype"]
|
100
110
|
answers.each do |answer|
|
101
|
-
res << PDNSResult.new(self.class.name, response_time, query, answer, rrtype, time_first, time_last, count)
|
111
|
+
res << PDNSResult.new(self.class.name, response_time, query, answer, rrtype, nil, time_first, time_last, count, TLPSecurityControl.new('yellow'))
|
102
112
|
end
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
113
|
+
end
|
114
|
+
res
|
115
|
+
rescue Exception => e
|
116
|
+
$stderr.puts "#{self.class.name} Exception: #{e}"
|
117
|
+
puts e.backtrace
|
118
|
+
raise e
|
108
119
|
end
|
109
120
|
end
|
110
121
|
end
|
111
|
-
end
|
122
|
+
end
|
@@ -8,7 +8,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
8
8
|
# The Provider module contains all the Passive DNS provider client code
|
9
9
|
module Provider
|
10
10
|
# Queries FarSight's passive DNS database
|
11
|
-
|
11
|
+
class DNSDB < PassiveDB
|
12
12
|
# Sets the modules self-reported name to "DNSDB"
|
13
13
|
def self.name
|
14
14
|
"DNSDB"
|
@@ -39,80 +39,81 @@ module PassiveDNS #:nodoc: don't document this
|
|
39
39
|
#
|
40
40
|
# PassiveDNS::Provider::DNSDB.new(options)
|
41
41
|
#
|
42
|
-
|
43
|
-
|
42
|
+
def initialize(options={})
|
43
|
+
@debug = options[:debug] || false
|
44
|
+
@timeout = options[:timeout] || 20
|
44
45
|
@key = options["APIKEY"] || raise("APIKEY option required for #{self.class}")
|
45
46
|
@base = options["URL"] || "https://api.dnsdb.info/lookup"
|
46
|
-
|
47
|
+
end
|
47
48
|
|
48
49
|
# Takes a label (either a domain or an IP address) and returns
|
49
50
|
# an array of PassiveDNS::PDNSResult instances with the answers to the query
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
51
|
+
def lookup(label, limit=nil)
|
52
|
+
$stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
|
53
|
+
Timeout::timeout(@timeout) {
|
54
|
+
url = nil
|
55
|
+
if label =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$/
|
56
|
+
label = label.gsub(/\//,',')
|
57
|
+
url = "#{@base}/rdata/ip/#{label}"
|
58
|
+
else
|
59
|
+
url = "#{@base}/rrset/name/#{label}"
|
60
|
+
end
|
61
|
+
url = URI.parse url
|
62
|
+
http = Net::HTTP.new(url.host, url.port)
|
63
|
+
http.use_ssl = (url.scheme == 'https')
|
64
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
65
|
+
http.verify_depth = 5
|
65
66
|
path = url.path
|
66
67
|
if limit
|
67
68
|
path << "?limit=#{limit}"
|
68
69
|
end
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
70
|
+
request = Net::HTTP::Get.new(path)
|
71
|
+
request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
|
72
|
+
request.add_field("X-API-Key", @key)
|
73
|
+
request.add_field("Accept", "application/json")
|
74
|
+
t1 = Time.now
|
75
|
+
response = http.request(request)
|
76
|
+
t2 = Time.now
|
77
|
+
$stderr.puts response.body if @debug
|
78
|
+
parse_json(response.body,t2-t1)
|
79
|
+
}
|
80
|
+
rescue Timeout::Error
|
81
|
+
$stderr.puts "#{self.class.name} lookup timed out: #{label}"
|
82
|
+
end
|
82
83
|
|
83
84
|
private
|
84
85
|
|
85
86
|
# parses the response of DNSDB's JSON reply to generate an array of PDNSResult
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
87
|
+
def parse_json(page,response_time)
|
88
|
+
res = []
|
89
|
+
raise "Error: unable to parse request" if page =~ /Error: unable to parse request/
|
90
|
+
rows = page.split(/\n/)
|
91
|
+
rows.each do |row|
|
92
|
+
record = JSON.parse(row)
|
92
93
|
answers = record['rdata']
|
93
|
-
|
94
|
+
answers = [record['rdata']] if record['rdata'].class == String
|
94
95
|
query = record['rrname'].gsub!(/\.$/,'')
|
95
96
|
rrtype = record['rrtype']
|
96
97
|
firstseen = Time.at(record['time_first'].to_i)
|
97
98
|
lastseen = Time.at(record['time_last'].to_i)
|
98
99
|
count = record['count']
|
99
100
|
|
100
|
-
|
101
|
+
answers.each do |answer|
|
101
102
|
answer.gsub!(/\.$/,'')
|
102
|
-
|
103
|
-
|
104
|
-
0,firstseen,lastseen,count)
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
103
|
+
if record['time_first']
|
104
|
+
res << PDNSResult.new(self.class.name,response_time,query,answer,rrtype,
|
105
|
+
0,firstseen,lastseen,count, 'yellow')
|
106
|
+
else
|
107
|
+
res << PDNSResult.new(self.class.name,response_time,query,answer,rrtype)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
res
|
112
|
+
rescue Exception => e
|
113
|
+
$stderr.puts "#{self.class.name} Exception: #{e}"
|
114
|
+
$stderr.puts page
|
115
|
+
raise e
|
116
|
+
end
|
117
|
+
end
|
117
118
|
end
|
118
|
-
end
|
119
|
+
end
|