passivedns-client 2.1.7 → 2.1.13
Sign up to get free protection for your applications and to get access to all the features.
- 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}"
|