passivedns-client 2.1.7 → 2.1.13
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/README.md +34 -49
- data/Rakefile +0 -0
- data/lib/passivedns/client.rb +6 -0
- data/lib/passivedns/client/cli.rb +14 -11
- data/lib/passivedns/client/provider/circl.rb +22 -6
- data/lib/passivedns/client/provider/dnsdb.rb +3 -2
- data/lib/passivedns/client/provider/passivetotal.rb +8 -3
- data/lib/passivedns/client/provider/riskiq.rb +22 -6
- data/lib/passivedns/client/provider/virustotal.rb +17 -3
- data/lib/passivedns/client/state.rb +2 -1
- data/lib/passivedns/client/version.rb +1 -1
- data/passivedns-client.gemspec +6 -6
- data/test/test_cli.rb +57 -43
- data/test/test_passivedns-client.rb +73 -166
- metadata +23 -28
- data/lib/passivedns/client/provider/bfk.rb +0 -107
- data/lib/passivedns/client/provider/cn360.rb +0 -111
- data/lib/passivedns/client/provider/mnemonic.rb +0 -111
- data/lib/passivedns/client/provider/tcpiputils.rb +0 -128
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8c2b55f21b59fb20d9167e1893d86aa500e3d020932d7b6e3fe5f2eed7e3113b
|
4
|
+
data.tar.gz: 7a48256ee4739a8a432a4a5f7aff6c7a9d8fcd1e5922b07caaf6d3557ffa5ca5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e75a301a616818187ba3ea036b2ae39b8b3b562c37115cf4b6d1ece009748838d76477ef58b353f654a81e890c1f5aaa2aeb151b45c23cb5bec692845f52f732
|
7
|
+
data.tar.gz: 6a549456ebbc19920f00129ee0371742790f288324ade26441349cd407d9e7f8f42e49ecaac49da3b59ed5ba36267fb925d77cfc33e996d58df4d8284a28f5d2
|
data/README.md
CHANGED
@@ -2,14 +2,10 @@
|
|
2
2
|
|
3
3
|
This rubygem queries the following Passive DNS databases:
|
4
4
|
|
5
|
-
* BFK.de
|
6
5
|
* CIRCL
|
7
6
|
* DNSDB (FarSight)
|
8
|
-
* Mnemonic
|
9
|
-
* PassiveDNS.cn (Qihoo 360 Technology Co.,Ltd)
|
10
7
|
* PassiveTotal
|
11
8
|
* RiskIQ
|
12
|
-
* TCPIPUtils
|
13
9
|
* VirusTotal
|
14
10
|
|
15
11
|
Passive DNS is a technique where IP to hostname mappings are made by recording the answers of other people's queries.
|
@@ -40,16 +36,8 @@ From version 2.0.0 on, all configuration keys for passive DNS providers are in o
|
|
40
36
|
|
41
37
|
[dnsdb]
|
42
38
|
APIKEY = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
43
|
-
[cn360]
|
44
|
-
API = http://some.web.address.for.their.api
|
45
|
-
API_ID = a username that is given when you register
|
46
|
-
API_KEY = a long and random password of sorts that is used along with the page request to generate a per page API key
|
47
|
-
[tcpiputils]
|
48
|
-
APIKEY = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
49
39
|
[virustotal]
|
50
40
|
APIKEY = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
51
|
-
[mnemonic]
|
52
|
-
APIKEY = 01234567890abcdef01234567890abcdef012345
|
53
41
|
[passivetotal]
|
54
42
|
USERNAME = tom@example.com
|
55
43
|
APIKEY = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
@@ -66,58 +54,55 @@ CIRCL also can use and authorization token. In that case, you should drop the U
|
|
66
54
|
AUTH_TOKEN = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
67
55
|
|
68
56
|
## Getting Access
|
69
|
-
* 360.cn : http://www.passivedns.cn
|
70
|
-
* BFK.de : No registration required, but please, please ready their usage policy at http://www.bfk.de/bfk_dnslogger.html
|
71
57
|
* CIRCL : https://www.circl.lu/services/passive-dns/
|
72
58
|
* DNSDB (Farsight Security) : https://api.dnsdb.info/
|
73
|
-
* Mnemonic : mss .at. mnemonic.no
|
74
59
|
* PassiveTotal : https://www.passivetotal.org
|
75
60
|
* RiskIQ : https://github.com/RiskIQ/python_api/blob/master/LICENSE
|
76
|
-
* TCPIPUtils : http://www.tcpiputils.com/premium-access
|
77
61
|
* VirusTotal : https://www.virustotal.com
|
78
62
|
|
79
63
|
## Usage
|
80
64
|
|
81
65
|
require 'passivedns/client'
|
82
66
|
|
83
|
-
c = PassiveDNS::Client.new(['
|
67
|
+
c = PassiveDNS::Client.new(['riskiq','dnsdb'])
|
84
68
|
results = c.query("example.com")
|
85
69
|
|
86
70
|
|
87
71
|
Or use the included tool...
|
88
72
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
73
|
+
Usage: bin/pdnstool [-d [cdprv]] [-g|-v|-m|-c|-x|-y|-j|-t] [-os <sep>] [-f <file>] [-r#|-w#|-v] [-l <count>] [--config <file>] <ip|domain|cidr>
|
74
|
+
Passive DNS Providers
|
75
|
+
-dcdprv uses all of the available passive dns database
|
76
|
+
-dc use CIRCL
|
77
|
+
-dd use DNSDB
|
78
|
+
-dp use PassiveTotal
|
79
|
+
-dr use RiskIQ
|
80
|
+
-dv use VirusTotal
|
81
|
+
-dvr uses VirusTotal and RiskIQ (for example)
|
82
|
+
|
83
|
+
Output Formatting
|
84
|
+
-g link-nodal GDF visualization definition
|
85
|
+
-z link-nodal graphviz visualization definition
|
86
|
+
-m link-nodal graphml visualization definition
|
87
|
+
-c CSV
|
88
|
+
-x XML
|
89
|
+
-y YAML
|
90
|
+
-j JSON
|
91
|
+
-t ASCII text (default)
|
92
|
+
-s <sep> specifies a field separator for text output, default is tab
|
93
|
+
|
94
|
+
State and Recursion
|
95
|
+
-f[file] specifies a sqlite3 database used to read the current state - useful for large result sets and generating graphs of previous runs.
|
96
|
+
-r# specifies the levels of recursion to pull. **WARNING** This is quite taxing on the pDNS servers, so use judiciously (never more than 3 or so) or find yourself blocked!
|
97
|
+
-w# specifies the amount of time to wait, in seconds, between queries (Default: 0)
|
98
|
+
-l <count> limits the number of records returned per passive dns database queried.
|
99
|
+
|
100
|
+
Specifying a Configuration File
|
101
|
+
--config <file> specifies a config file. default: /home/chris/.passivedns-client
|
102
|
+
|
103
|
+
Getting Help
|
104
|
+
-h hello there. This option produces this helpful help information on how to access help.
|
105
|
+
-v debugging information
|
121
106
|
|
122
107
|
## Writing Your Own Database Adaptor
|
123
108
|
|
data/Rakefile
CHANGED
File without changes
|
data/lib/passivedns/client.rb
CHANGED
@@ -92,6 +92,12 @@ module PassiveDNS # :nodoc:
|
|
92
92
|
end
|
93
93
|
end
|
94
94
|
|
95
|
+
def timeout=(t)
|
96
|
+
@pdnsdbs.each do |pdnsdb|
|
97
|
+
pdnsdb.timeout = t
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
95
101
|
# perform the query lookup accross all configured PassiveDNS providers
|
96
102
|
def query(item, limit=nil)
|
97
103
|
threads = []
|
@@ -58,7 +58,8 @@ module PassiveDNS # :nodoc:
|
|
58
58
|
[ '--sqlite3', '-f', GetoptLong::REQUIRED_ARGUMENT ],
|
59
59
|
[ '--recurse', '-r', GetoptLong::REQUIRED_ARGUMENT ],
|
60
60
|
[ '--wait', '-w', GetoptLong::REQUIRED_ARGUMENT ],
|
61
|
-
[ '--limit', '-l', GetoptLong::REQUIRED_ARGUMENT ]
|
61
|
+
[ '--limit', '-l', GetoptLong::REQUIRED_ARGUMENT ],
|
62
|
+
[ '--config', GetoptLong::REQUIRED_ARGUMENT ]
|
62
63
|
)
|
63
64
|
|
64
65
|
letter_map = get_letter_map
|
@@ -74,7 +75,8 @@ module PassiveDNS # :nodoc:
|
|
74
75
|
:debug => false,
|
75
76
|
:sqlitedb => nil,
|
76
77
|
:limit => nil,
|
77
|
-
:help => false
|
78
|
+
:help => false,
|
79
|
+
:configfile => "#{ENV['HOME']}/.passivedns-client"
|
78
80
|
}
|
79
81
|
|
80
82
|
opts.each do |opt, arg|
|
@@ -121,6 +123,8 @@ module PassiveDNS # :nodoc:
|
|
121
123
|
options[:sqlitedb] = arg
|
122
124
|
when '--limit'
|
123
125
|
options[:limit] = arg.to_i
|
126
|
+
when '--config'
|
127
|
+
options[:configfile] = arg
|
124
128
|
else
|
125
129
|
options[:help] = true
|
126
130
|
end
|
@@ -129,12 +133,7 @@ module PassiveDNS # :nodoc:
|
|
129
133
|
ARGV.replace(origARGV)
|
130
134
|
|
131
135
|
if options[:pdnsdbs].length == 0
|
132
|
-
options[:pdnsdbs] << "
|
133
|
-
end
|
134
|
-
|
135
|
-
if options[:pdnsdbs].index("bfk") and options[:recursedepth] > 1 and options[:wait] < 60
|
136
|
-
options[:wait] = 60
|
137
|
-
$stderr.puts "Enforcing a minimal 60 second wait when using BFK for recursive crawling"
|
136
|
+
options[:pdnsdbs] << "virustotal"
|
138
137
|
end
|
139
138
|
|
140
139
|
if options[:debug]
|
@@ -158,13 +157,13 @@ module PassiveDNS # :nodoc:
|
|
158
157
|
def self.usage(letter_map)
|
159
158
|
databases = letter_map.keys.sort.join("")
|
160
159
|
help_text = ""
|
161
|
-
help_text << "Usage: #{$0} [-d [#{databases}]] [-g|-v|-m|-c|-x|-y|-j|-t] [-os <sep>] [-f <file>] [-r#|-w#|-v] [-l <count>] <ip|domain|cidr>\n"
|
160
|
+
help_text << "Usage: #{$0} [-d [#{databases}]] [-g|-v|-m|-c|-x|-y|-j|-t] [-os <sep>] [-f <file>] [-r#|-w#|-v] [-l <count>] [--config <file>] <ip|domain|cidr>\n"
|
162
161
|
help_text << "Passive DNS Providers\n"
|
163
162
|
help_text << " -d#{databases} uses all of the available passive dns database\n"
|
164
163
|
letter_map.keys.sort.each do |l|
|
165
164
|
help_text << " -d#{l} use #{letter_map[l][0]}\n"
|
166
165
|
end
|
167
|
-
help_text << " -
|
166
|
+
help_text << " -dvr uses VirusTotal and RiskIQ (for example)\n"
|
168
167
|
help_text << "\n"
|
169
168
|
help_text << "Output Formatting\n"
|
170
169
|
help_text << " -g link-nodal GDF visualization definition\n"
|
@@ -183,6 +182,9 @@ module PassiveDNS # :nodoc:
|
|
183
182
|
help_text << " -w# specifies the amount of time to wait, in seconds, between queries (Default: 0)\n"
|
184
183
|
help_text << " -l <count> limits the number of records returned per passive dns database queried.\n"
|
185
184
|
help_text << "\n"
|
185
|
+
help_text << "Specifying a Configuration File\n"
|
186
|
+
help_text << " --config <file> specifies a config file. default: #{ENV['HOME']}/.passivedns-client\n"
|
187
|
+
help_text << "\n"
|
186
188
|
help_text << "Getting Help\n"
|
187
189
|
help_text << " -h hello there. This option produces this helpful help information on how to access help.\n"
|
188
190
|
help_text << " -v debugging information\n"
|
@@ -249,6 +251,7 @@ module PassiveDNS # :nodoc:
|
|
249
251
|
else
|
250
252
|
state = PassiveDNS::PDNSToolState.new
|
251
253
|
end
|
254
|
+
state
|
252
255
|
end
|
253
256
|
|
254
257
|
# main method, takes command-line arguments and performs the desired queries and outputs
|
@@ -264,7 +267,7 @@ module PassiveDNS # :nodoc:
|
|
264
267
|
state = create_state(options[:sqlitedb])
|
265
268
|
state.debug = options[:debug]
|
266
269
|
|
267
|
-
pdnsclient = PassiveDNS::Client.new(options[:pdnsdbs])
|
270
|
+
pdnsclient = PassiveDNS::Client.new(options[:pdnsdbs], options[:configfile])
|
268
271
|
pdnsclient.debug = options[:debug]
|
269
272
|
|
270
273
|
if items.length > 0
|
@@ -47,6 +47,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
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"]
|
@@ -57,10 +58,16 @@ module PassiveDNS #:nodoc: don't document this
|
|
57
58
|
# an array of PassiveDNS::PDNSResult instances with the answers to the query
|
58
59
|
def lookup(label, limit=nil)
|
59
60
|
$stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
|
60
|
-
|
61
|
+
recs = []
|
62
|
+
Timeout::timeout(@timeout) {
|
61
63
|
url = @url+"/"+label
|
62
64
|
$stderr.puts "DEBUG: #{self.class.name} url = #{url}" if @debug
|
63
|
-
|
65
|
+
begin
|
66
|
+
url = URI.parse url
|
67
|
+
rescue URI::InvalidURIError
|
68
|
+
$stderr.puts "ERROR: Invalid address: #{url}"
|
69
|
+
return recs
|
70
|
+
end
|
64
71
|
http = Net::HTTP.new(url.host, url.port)
|
65
72
|
http.use_ssl = (url.scheme == 'https')
|
66
73
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
@@ -74,17 +81,26 @@ module PassiveDNS #:nodoc: don't document this
|
|
74
81
|
request.add_field("Authorization", @auth_token)
|
75
82
|
end
|
76
83
|
t1 = Time.now
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
80
95
|
if limit
|
81
96
|
recs[0,limit]
|
82
97
|
else
|
83
98
|
recs
|
84
99
|
end
|
85
100
|
}
|
86
|
-
rescue Timeout::Error
|
101
|
+
rescue Timeout::Error
|
87
102
|
$stderr.puts "#{self.class.name} lookup timed out: #{label}"
|
103
|
+
recs
|
88
104
|
end
|
89
105
|
|
90
106
|
private
|
@@ -41,6 +41,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
41
41
|
#
|
42
42
|
def initialize(options={})
|
43
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
|
@@ -49,7 +50,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
49
50
|
# an array of PassiveDNS::PDNSResult instances with the answers to the query
|
50
51
|
def lookup(label, limit=nil)
|
51
52
|
$stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
|
52
|
-
Timeout::timeout(
|
53
|
+
Timeout::timeout(@timeout) {
|
53
54
|
url = nil
|
54
55
|
if label =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$/
|
55
56
|
label = label.gsub(/\//,',')
|
@@ -76,7 +77,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
76
77
|
$stderr.puts response.body if @debug
|
77
78
|
parse_json(response.body,t2-t1)
|
78
79
|
}
|
79
|
-
rescue Timeout::Error
|
80
|
+
rescue Timeout::Error
|
80
81
|
$stderr.puts "#{self.class.name} lookup timed out: #{label}"
|
81
82
|
end
|
82
83
|
|
@@ -54,6 +54,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
54
54
|
#
|
55
55
|
def initialize(options={})
|
56
56
|
@debug = options[:debug] || false
|
57
|
+
@timeout = options[:timeout] || 20
|
57
58
|
@username = options["USERNAME"] || raise("#{self.class.name} requires a USERNAME")
|
58
59
|
@apikey = options["APIKEY"] || raise("#{self.class.name} requires an APIKEY")
|
59
60
|
@url = options["URL"] || "https://api.passivetotal.org/v2/dns/passive"
|
@@ -63,7 +64,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
63
64
|
# an array of PassiveDNS::PDNSResult instances with the answers to the query
|
64
65
|
def lookup(label, limit=nil)
|
65
66
|
$stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
|
66
|
-
Timeout::timeout(
|
67
|
+
Timeout::timeout(@timeout) {
|
67
68
|
url = @url+"?query=#{label}"
|
68
69
|
$stderr.puts "DEBUG: #{self.class.name} url = #{url}" if @debug
|
69
70
|
url = URI.parse url
|
@@ -85,7 +86,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
85
86
|
recs
|
86
87
|
end
|
87
88
|
}
|
88
|
-
rescue Timeout::Error
|
89
|
+
rescue Timeout::Error
|
89
90
|
$stderr.puts "#{self.class.name} lookup timed out: #{label}"
|
90
91
|
end
|
91
92
|
|
@@ -93,8 +94,12 @@ module PassiveDNS #:nodoc: don't document this
|
|
93
94
|
|
94
95
|
# parses the response of passivetotals's JSON reply to generate an array of PDNSResult
|
95
96
|
def parse_json(page,query,response_time=0)
|
96
|
-
|
97
|
+
res = []
|
97
98
|
data = JSON.parse(page)
|
99
|
+
pp data
|
100
|
+
if data['message']
|
101
|
+
raise "#{self.class.name} Error: #{data['message']}"
|
102
|
+
end
|
98
103
|
query = data['queryValue']
|
99
104
|
if data['results']
|
100
105
|
data['results'].each do |row|
|
@@ -46,10 +46,11 @@ module PassiveDNS #:nodoc: don't document this
|
|
46
46
|
#
|
47
47
|
def initialize(options={})
|
48
48
|
@debug = options[:debug] || false
|
49
|
+
@timeout = options[:timeout] || 20
|
49
50
|
@token = options["API_TOKEN"] || raise("#{self.class.name} requires an API_TOKEN")
|
50
51
|
@privkey = options["API_PRIVATE_KEY"] || raise("#{self.class.name} requires an API_PRIVATE_KEY")
|
51
|
-
@server = options["API_SERVER"] || "ws.riskiq.net"
|
52
52
|
@version = options["API_VERSION"] || "v1"
|
53
|
+
@server = options["API_SERVER"] || api_settings[@version][:server]
|
53
54
|
@url = "https://#{@server}/#{@version}"
|
54
55
|
end
|
55
56
|
|
@@ -57,7 +58,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
57
58
|
# an array of PassiveDNS::PDNSResult instances with the answers to the query
|
58
59
|
def lookup(label, limit=nil)
|
59
60
|
$stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
|
60
|
-
Timeout::timeout(
|
61
|
+
Timeout::timeout(@timeout) {
|
61
62
|
url = nil
|
62
63
|
params = {"rrType" => "", "maxResults" => limit || 1000}
|
63
64
|
|
@@ -65,8 +66,10 @@ module PassiveDNS #:nodoc: don't document this
|
|
65
66
|
url = @url+"/dns/data"
|
66
67
|
params["ip"] = label
|
67
68
|
else
|
68
|
-
|
69
|
-
|
69
|
+
resource = api_settings[@version][:resource]
|
70
|
+
param = api_settings[@version][:param]
|
71
|
+
url = @url+"/dns/#{resource}"
|
72
|
+
params[param] = label
|
70
73
|
end
|
71
74
|
url << "?"
|
72
75
|
params.each do |k,v|
|
@@ -95,16 +98,29 @@ module PassiveDNS #:nodoc: don't document this
|
|
95
98
|
recs
|
96
99
|
end
|
97
100
|
}
|
98
|
-
rescue Timeout::Error
|
101
|
+
rescue Timeout::Error
|
99
102
|
$stderr.puts "#{self.class.name} lookup timed out: #{label}"
|
100
103
|
end
|
101
104
|
|
102
105
|
private
|
103
106
|
|
107
|
+
def api_settings
|
108
|
+
@api_settings ||= {
|
109
|
+
'v1' => { server: "ws.riskiq.net", resource: 'name', param: 'name' },
|
110
|
+
'v2' => { server: "api.passivetotal.org", resource: 'passive', param: 'query' }
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
104
114
|
# parses the response of riskiq's JSON reply to generate an array of PDNSResult
|
105
115
|
def parse_json(page,query,response_time=0)
|
106
|
-
|
116
|
+
res = []
|
107
117
|
data = JSON.parse(page)
|
118
|
+
if data['message']
|
119
|
+
if data['message'] =~ /quota_exceeded/
|
120
|
+
$stderr.puts "ERROR: quota exceeded."
|
121
|
+
return res
|
122
|
+
end
|
123
|
+
end
|
108
124
|
if data['records']
|
109
125
|
data['records'].each do |record|
|
110
126
|
name = record['name'].gsub!(/\.$/,'')
|
@@ -41,6 +41,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
41
41
|
#
|
42
42
|
def initialize(options={})
|
43
43
|
@debug = options[:debug] || false
|
44
|
+
@timeout = options[:timeout] || 20
|
44
45
|
@apikey = options["APIKEY"] || raise("#{self.class.name} requires an APIKEY. See README.md")
|
45
46
|
@url = options["URL"] || "https://www.virustotal.com/vtapi/v2/"
|
46
47
|
end
|
@@ -49,7 +50,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
49
50
|
# an array of PassiveDNS::PDNSResult instances with the answers to the query
|
50
51
|
def lookup(label, limit=nil)
|
51
52
|
$stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
|
52
|
-
Timeout::timeout(
|
53
|
+
Timeout::timeout(@timeout) {
|
53
54
|
url = nil
|
54
55
|
if label =~ /^[\d\.]+$/
|
55
56
|
url = "#{@url}ip-address/report?ip=#{label}&apikey=#{@apikey}"
|
@@ -57,7 +58,12 @@ module PassiveDNS #:nodoc: don't document this
|
|
57
58
|
url = "#{@url}domain/report?domain=#{label}&apikey=#{@apikey}"
|
58
59
|
end
|
59
60
|
$stderr.puts "DEBUG: #{self.class.name} url = #{url}" if @debug
|
60
|
-
|
61
|
+
begin
|
62
|
+
url = URI.parse url
|
63
|
+
rescue URI::InvalidURIError
|
64
|
+
$stderr.puts "ERROR: Invalid address: #{url}"
|
65
|
+
return
|
66
|
+
end
|
61
67
|
http = Net::HTTP.new(url.host, url.port)
|
62
68
|
http.use_ssl = (url.scheme == 'https')
|
63
69
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
@@ -66,6 +72,10 @@ module PassiveDNS #:nodoc: don't document this
|
|
66
72
|
request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
|
67
73
|
t1 = Time.now
|
68
74
|
response = http.request(request)
|
75
|
+
if response.code.to_i == 204
|
76
|
+
$stderr.puts "DEBUG: empty response from server" if @debug
|
77
|
+
return
|
78
|
+
end
|
69
79
|
t2 = Time.now
|
70
80
|
recs = parse_json(response.body, label, t2-t1)
|
71
81
|
if limit
|
@@ -74,7 +84,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
74
84
|
recs
|
75
85
|
end
|
76
86
|
}
|
77
|
-
rescue Timeout::Error
|
87
|
+
rescue Timeout::Error
|
78
88
|
$stderr.puts "#{self.class.name} lookup timed out: #{label}"
|
79
89
|
end
|
80
90
|
|
@@ -83,6 +93,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
83
93
|
# parses the response of virustotal's JSON reply to generate an array of PDNSResult
|
84
94
|
def parse_json(page,query,response_time=0)
|
85
95
|
res = []
|
96
|
+
return res if !page
|
86
97
|
data = JSON.parse(page)
|
87
98
|
if data['resolutions']
|
88
99
|
data['resolutions'].each do |row|
|
@@ -94,6 +105,9 @@ module PassiveDNS #:nodoc: don't document this
|
|
94
105
|
end
|
95
106
|
end
|
96
107
|
end
|
108
|
+
if data['response_code'] == 0
|
109
|
+
$stderr.puts "DEBUG: server returned error: #{data['verbose_msg']}" if @debug
|
110
|
+
end
|
97
111
|
res
|
98
112
|
rescue Exception => e
|
99
113
|
$stderr.puts "VirusTotal Exception: #{e}"
|