passivedns-client 2.0.6 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,118 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'openssl'
4
+ require 'json'
5
+
6
+ # Please read http://www.tcpiputils.com/terms-of-service under automated requests
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 TCPIPUtils's passive DNS database
12
+ class TCPIPUtils < PassiveDB
13
+ # Sets the modules self-reported name to "TCPIPUtils"
14
+ def self.name
15
+ "TCPIPUtils"
16
+ end
17
+ # Sets the configuration section name to "tcpiputils"
18
+ def self.config_section_name
19
+ "tcpiputils"
20
+ end
21
+ # Sets the command line database argument to "t"
22
+ def self.option_letter
23
+ "t"
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
+ # * "APIKEY" REQUIRED: The API key associated with TCPIPUtils
31
+ # * "URL" Alternate url for testing. Defaults to "https://www.utlsapi.com/api.php?version=1.0&apikey="
32
+ #
33
+ # === Example Instantiation
34
+ #
35
+ # options = {
36
+ # :debug => true,
37
+ # "APIKEY" => "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
38
+ # "URL" => "https://www.utlsapi.com/api.php?version=1.0&apikey="
39
+ # }
40
+ #
41
+ # PassiveDNS::Provider::TCPIPUtils.new(options)
42
+ #
43
+ def initialize(options={})
44
+ @debug = options[:debug] || false
45
+ @apikey = options["APIKEY"] || raise("#{self.class.name} requires an APIKEY. See README.md")
46
+ @url = options["URL"] || "https://www.utlsapi.com/api.php?version=1.0&apikey="
47
+ end
48
+
49
+ # Takes a label (either a domain or an IP address) and returns
50
+ # an array of PassiveDNS::PDNSResult instances with the answers to the query
51
+ def lookup(label, limit=nil)
52
+ $stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
53
+ type = (label.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) ? "domainneighbors" : "domainipdnshistory"
54
+ url = "#{@url}#{@apikey}&type=#{type}&q=#{label}"
55
+ recs = []
56
+ Timeout::timeout(240) {
57
+ url = URI.parse url
58
+ http = Net::HTTP.new(url.host, url.port)
59
+ http.use_ssl = (url.scheme == 'https')
60
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
61
+ http.verify_depth = 5
62
+ request = Net::HTTP::Get.new(url.path+"?"+url.query)
63
+ request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
64
+ t1 = Time.now
65
+ response = http.request(request)
66
+ delta = (Time.now - t1).to_f
67
+ reply = JSON.parse(response.body)
68
+ if reply["status"] and reply["status"] == "succeed"
69
+ question = reply["data"]["question"]
70
+ recs = format_recs(reply["data"], question, delta)
71
+ elsif reply["status"] and reply["status"] == "error"
72
+ raise "#{self.class.name}: error from web API: #{reply["data"]}"
73
+ end
74
+ if limit
75
+ recs[0,limit]
76
+ else
77
+ recs
78
+ end
79
+ }
80
+ rescue Timeout::Error => e
81
+ $stderr.puts "#{self.class.name} lookup timed out: #{label}"
82
+ end
83
+
84
+ private
85
+
86
+ # translates the data structure derived from of tcpiputils's JSON reply
87
+ def format_recs(reply_data, question, delta)
88
+ recs = []
89
+ reply_data.each do |key, data|
90
+ case key
91
+ when "ipv4"
92
+ data.each do |rec|
93
+ recs << PDNSResult.new(self.class.name, delta, question, rec["ip"], "A", nil, nil, rec["updatedate"], nil)
94
+ end
95
+ when "ipv6"
96
+ data.each do |rec|
97
+ recs << PDNSResult.new(self.class.name, delta, question, rec["ip"], "AAAA", nil, nil, rec["updatedate"], nil)
98
+ end
99
+ when "dns"
100
+ data.each do |rec|
101
+ recs << PDNSResult.new(self.class.name, delta, question, rec["dns"], "NS", nil, nil, rec["updatedate"], nil)
102
+ end
103
+ when "mx"
104
+ data.each do |rec|
105
+ recs << PDNSResult.new(self.class.name, delta, question, rec["dns"], "MX", nil, nil, rec["updatedate"], nil)
106
+ end
107
+ when "domains"
108
+ data.each do |rec|
109
+ recs << PDNSResult.new(self.class.name, delta, rec, question, "A", nil, nil, nil, nil)
110
+ end
111
+ end
112
+ end
113
+ recs
114
+ end
115
+
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,105 @@
1
+ # DESCRIPTION: this is a module for pdns.rb, primarily used by pdnstool.rb, to query VirusTotal's passive DNS database
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'openssl'
5
+
6
+ module PassiveDNS #:nodoc: don't document this
7
+ # The Provider module contains all the Passive DNS provider client code
8
+ module Provider
9
+ # Queries VirusTotal's passive DNS database
10
+ class VirusTotal < PassiveDB
11
+ # Sets the modules self-reported name to "VirusTotal"
12
+ def self.name
13
+ "VirusTotal"
14
+ end
15
+ # Sets the configuration section name to "virustotal"
16
+ def self.config_section_name
17
+ "virustotal"
18
+ end
19
+ # Sets the command line database argument to "v"
20
+ def self.option_letter
21
+ "v"
22
+ end
23
+
24
+ # :debug enables verbose logging to standard output
25
+ attr_accessor :debug
26
+
27
+ # === Options
28
+ # * :debug Sets the debug flag for the module
29
+ # * "APIKEY" Mandatory. API Key associated with your VirusTotal account
30
+ # * "URL" Alternate url for testing. Defaults to https://www.virustotal.com/vtapi/v2/
31
+ #
32
+ # === Example Instantiation
33
+ #
34
+ # options = {
35
+ # :debug => true,
36
+ # "APIKEY" => "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
37
+ # "URL" => "https://www.virustotal.com/vtapi/v2/"
38
+ # }
39
+ #
40
+ # PassiveDNS::Provider::VirusTotal.new(options)
41
+ #
42
+ def initialize(options={})
43
+ @debug = options[:debug] || false
44
+ @apikey = options["APIKEY"] || raise("#{self.class.name} requires an APIKEY. See README.md")
45
+ @url = options["URL"] || "https://www.virustotal.com/vtapi/v2/"
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\.]+$/
55
+ url = "#{@url}ip-address/report?ip=#{label}&apikey=#{@apikey}"
56
+ else
57
+ url = "#{@url}domain/report?domain=#{label}&apikey=#{@apikey}"
58
+ end
59
+ $stderr.puts "DEBUG: #{self.class.name} url = #{url}" if @debug
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
+ request = Net::HTTP::Get.new(url.path+"?"+url.query)
66
+ request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
67
+ t1 = Time.now
68
+ response = http.request(request)
69
+ t2 = Time.now
70
+ recs = parse_json(response.body, label, t2-t1)
71
+ if limit
72
+ recs[0,limit]
73
+ else
74
+ recs
75
+ end
76
+ }
77
+ rescue Timeout::Error => e
78
+ $stderr.puts "#{self.class.name} lookup timed out: #{label}"
79
+ end
80
+
81
+ private
82
+
83
+ # parses the response of virustotal's JSON reply to generate an array of PDNSResult
84
+ def parse_json(page,query,response_time=0)
85
+ res = []
86
+ # need to remove the json_class tag or the parser will crap itself trying to find a class to align it to
87
+ data = JSON.parse(page)
88
+ if data['resolutions']
89
+ data['resolutions'].each do |row|
90
+ if row['ip_address']
91
+ res << PDNSResult.new(self.class.name,response_time,query,row['ip_address'],'A',nil,nil,row['last_resolved'])
92
+ elsif row['hostname']
93
+ res << PDNSResult.new(self.class.name,response_time,row['hostname'],query,'A',nil,nil,row['last_resolved'])
94
+ end
95
+ end
96
+ end
97
+ res
98
+ rescue Exception => e
99
+ $stderr.puts "VirusTotal Exception: #{e}"
100
+ raise e
101
+ end
102
+
103
+ end
104
+ end
105
+ end
@@ -2,31 +2,39 @@ require 'sqlite3'
2
2
  require 'yaml'
3
3
  require 'structformatter'
4
4
 
5
- module PassiveDNS
5
+ module PassiveDNS # :nodoc:
6
+ # struct to hold pending entries for query
6
7
  class PDNSQueueEntry < Struct.new(:query, :state, :level); end
7
8
 
9
+ # holds state in memory of the queue to be queried, records returned, and the level of recursion
8
10
  class PDNSToolState
11
+ # :debug enables verbose logging to standard output
9
12
  attr_accessor :debug
13
+ # :level is the recursion depth
10
14
  attr_reader :level
11
15
 
16
+ # creates a new, blank PDNSToolState instance
12
17
  def initialize
13
18
  @queue = []
14
19
  @recs = []
15
20
  @level = 0
16
21
  end
17
-
22
+
23
+ # returns the next record
18
24
  def next_result
19
25
  @recs.each do |rec|
20
26
  yield rec
21
27
  end
22
28
  end
23
29
 
30
+ # adds the record to the list of records received and tries to add the answer and query back to the queue for future query
24
31
  def add_result(res)
25
32
  @recs << res
26
33
  add_query(res.answer,'pending')
27
34
  add_query(res.query,'pending')
28
35
  end
29
36
 
37
+ # sets the state of a given query
30
38
  def update_query(query,state)
31
39
  @queue.each do |q|
32
40
  if q.query == query
@@ -37,6 +45,7 @@ module PassiveDNS
37
45
  end
38
46
  end
39
47
 
48
+ # returns the state of a provided query
40
49
  def get_state(query)
41
50
  @queue.each do |q|
42
51
  if q.query == query
@@ -46,6 +55,7 @@ module PassiveDNS
46
55
  false
47
56
  end
48
57
 
58
+ # adding a query to the queue of things to be queried, but only if the query isn't already queued or answered
49
59
  def add_query(query,state,level=@level+1)
50
60
  if query =~ /^\d+ \w+\./
51
61
  query = query.split(/ /,2)[1]
@@ -55,6 +65,7 @@ module PassiveDNS
55
65
  @queue << PDNSQueueEntry.new(query,state,level)
56
66
  end
57
67
 
68
+ # returns each query waiting on the queue
58
69
  def each_query(max_level=20)
59
70
  @queue.each do |q|
60
71
  if q.state == 'pending' or q.state == 'failed'
@@ -67,6 +78,7 @@ module PassiveDNS
67
78
  end
68
79
  end
69
80
 
81
+ # transforms a set of results into GDF syntax
70
82
  def to_gdf
71
83
  output = "nodedef> name,description VARCHAR(12),color,style\n"
72
84
  # IP "$node2,,white,1"
@@ -98,6 +110,7 @@ module PassiveDNS
98
110
  output
99
111
  end
100
112
 
113
+ # transforms a set of results into graphviz syntax
101
114
  def to_graphviz
102
115
  colors = {"MX" => "green", "A" => "blue", "CNAME" => "pink", "NS" => "red", "SOA" => "white", "PTR" => "purple", "TXT" => "brown"}
103
116
  output = "graph pdns {\n"
@@ -119,6 +132,7 @@ module PassiveDNS
119
132
  output += "}\n"
120
133
  end
121
134
 
135
+ # transforms a set of results into graphml syntax
122
136
  def to_graphml
123
137
  output = '<?xml version="1.0" encoding="UTF-8"?>
124
138
  <graphml xmlns="http://graphml.graphdrawing.org/xmlns"
@@ -141,6 +155,7 @@ module PassiveDNS
141
155
  output += '</graph></graphml>'+"\n"
142
156
  end
143
157
 
158
+ # transforms a set of results into XML
144
159
  def to_xml
145
160
  output = '<?xml version="1.0" encoding="UTF-8" ?>'+"\n"
146
161
  output += "<report>\n"
@@ -152,6 +167,7 @@ module PassiveDNS
152
167
  output += "</report>\n"
153
168
  end
154
169
 
170
+ # transforms a set of results into YAML
155
171
  def to_yaml
156
172
  output = ""
157
173
  next_result do |rec|
@@ -160,6 +176,7 @@ module PassiveDNS
160
176
  output
161
177
  end
162
178
 
179
+ # transforms a set of results into JSON
163
180
  def to_json
164
181
  output = "[\n"
165
182
  sep = ""
@@ -171,6 +188,7 @@ module PassiveDNS
171
188
  output += "\n]\n"
172
189
  end
173
190
 
191
+ # transforms a set of results into a text string
174
192
  def to_s(sep="\t")
175
193
  output = ""
176
194
  next_result do |rec|
@@ -181,8 +199,11 @@ module PassiveDNS
181
199
  end # class PDNSToolState
182
200
 
183
201
 
202
+ # creates persistence to the tool state by leveraging SQLite3
184
203
  class PDNSToolStateDB < PDNSToolState
185
204
  attr_reader :level
205
+ # creates an SQLite3-based Passive DNS Client state
206
+ # only argument is the filename of the sqlite3 database
186
207
  def initialize(sqlitedb=nil)
187
208
  puts "PDNSToolState initialize #{sqlitedb}" if @debug
188
209
  @level = 0
@@ -204,6 +225,7 @@ module PassiveDNS
204
225
  end
205
226
  end
206
227
 
228
+ # creates the sqlite3 tables needed to track the state of this tool as itqueries and recurses
207
229
  def create_tables
208
230
  puts "creating tables" if @debug
209
231
  @sqlitedbh.execute("create table results (query, answer, rrtype, ttl, firstseen, lastseen, ts REAL)")
@@ -214,6 +236,7 @@ module PassiveDNS
214
236
  @sqlitedbh.execute("create index queue_state_idx on queue (state)")
215
237
  end
216
238
 
239
+ # returns the next record
217
240
  def next_result
218
241
  rows = @sqlitedbh.execute("select query, answer, rrtype, ttl, firstseen, lastseen from results order by ts")
219
242
  rows.each do |row|
@@ -221,6 +244,7 @@ module PassiveDNS
221
244
  end
222
245
  end
223
246
 
247
+ # adds the record to the list of records received and tries to add the answer and query back to the queue for future query
224
248
  def add_result(res)
225
249
  puts "adding result: #{res.to_s}" if @debug
226
250
  curtime = Time.now().to_f
@@ -230,6 +254,7 @@ module PassiveDNS
230
254
  add_query(res.query,'pending')
231
255
  end
232
256
 
257
+ # adding a query to the queue of things to be queried, but only if the query isn't already queued or answered
233
258
  def add_query(query,state,level=@level+1)
234
259
  return if get_state(query)
235
260
  curtime = Time.now().to_f
@@ -240,10 +265,12 @@ module PassiveDNS
240
265
  end
241
266
  end
242
267
 
268
+ # sets the state of a given query
243
269
  def update_query(query,state)
244
270
  @sqlitedbh.execute("update queue set state = '#{state}' where query = '#{query}'")
245
271
  end
246
272
 
273
+ # returns each query waiting on the queue
247
274
  def get_state(query)
248
275
  rows = @sqlitedbh.execute("select state from queue where query = '#{query}'")
249
276
  if rows
@@ -254,6 +281,7 @@ module PassiveDNS
254
281
  false
255
282
  end
256
283
 
284
+ # returns each query waiting on the queue
257
285
  def each_query(max_level=20)
258
286
  puts "each_query max_level=#{max_level} curlevel=#{@level}" if @debug
259
287
  rows = @sqlitedbh.execute("select query, state, level from queue where state = 'failed' or state = 'pending' order by level limit 1")
@@ -1,5 +1,7 @@
1
- module PassiveDNS
1
+ module PassiveDNS # :nodoc:
2
+ # coodinates the lookups accross all configured PassiveDNS providers
2
3
  class Client
3
- VERSION = "2.0.6"
4
+ # version of PassiveDNS::Client
5
+ VERSION = "2.1.0"
4
6
  end
5
7
  end
data/test/test_cli.rb CHANGED
@@ -13,22 +13,23 @@ require_relative '../lib/passivedns/client/cli.rb'
13
13
  class TestCLI < Minitest::Test
14
14
  def test_letter_map
15
15
  letter_map = PassiveDNS::CLI.get_letter_map
16
- assert_equal("3bcdmptv", letter_map.keys.sort.join(""))
16
+ assert_equal("3bcdmprtv", letter_map.keys.sort.join(""))
17
17
  end
18
18
 
19
19
  def test_help_text
20
20
  helptext = PassiveDNS::CLI.run(["--help"])
21
21
  helptext.gsub!(/Usage: .*?\[/, "Usage: [")
22
22
  assert_equal(
23
- "Usage: [-d [3bcdmptv]] [-g|-v|-m|-c|-x|-y|-j|-t] [-os <sep>] [-f <file>] [-r#|-w#|-v] [-l <count>] <ip|domain|cidr>
23
+ "Usage: [-d [3bcdmprtv]] [-g|-v|-m|-c|-x|-y|-j|-t] [-os <sep>] [-f <file>] [-r#|-w#|-v] [-l <count>] <ip|domain|cidr>
24
24
  Passive DNS Providers
25
- -d3bcdmptv uses all of the available passive dns database
25
+ -d3bcdmprtv uses all of the available passive dns database
26
26
  -d3 use 360.cn
27
27
  -db use BFK.de
28
28
  -dc use CIRCL
29
29
  -dd use DNSDB
30
30
  -dm use Mnemonic
31
31
  -dp use PassiveTotal
32
+ -dr use RiskIQ
32
33
  -dt use TCPIPUtils
33
34
  -dv use VirusTotal
34
35
  -dvt uses VirusTotal and TCPIPUtils (for example)
@@ -84,8 +85,8 @@ Getting Help
84
85
  assert_equal(options_target, options)
85
86
  assert_equal([], items)
86
87
 
87
- options_target[:pdnsdbs] = ["circl", "dnsdb", "mnemonic"]
88
- options, items = PassiveDNS::CLI.parse_command_line(["-dcdm"])
88
+ options_target[:pdnsdbs] = ["circl", "dnsdb", "mnemonic", "riskiq"]
89
+ options, items = PassiveDNS::CLI.parse_command_line(["-dcdmr"])
89
90
  assert_equal(options_target, options)
90
91
  assert_equal([], items)
91
92