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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9fc3d708ad9c1dd55d73acf19b347589c8142292
4
- data.tar.gz: f48765e8140f0f84dd26a8912728190458ab3dbb
3
+ metadata.gz: 9bc0b0ff155e1b9570c05b45f22368b0cfa79a83
4
+ data.tar.gz: 4b2d805d77a29ef052c0003a116c21224166254f
5
5
  SHA512:
6
- metadata.gz: 6c6d15bd3b5e0c556b515512be6645b626139f9c0c2c42c8e48bb7347599edb2bcbb6ca0b011dd8c981a2fdd2d13f50e98f1d34935f81223a60039c237531052
7
- data.tar.gz: d14cf8d3a6e50d8e1708d4fa32d11655efec83403ca9ba62fd10c3246dac1b7930d59ace1245a1cd5079270d22fbe7eba3124897b0b6bd95c8518f6346c13e12
6
+ metadata.gz: 21dd2aefcc5074d626047c4cf4e798c33b9dc063199667b29a68614463b464e01f91fe72b285ecf2a3a752f8ef1f9a4082c1da797f6ed405950becefa32e5c55
7
+ data.tar.gz: 39c7c7ae6f931634c58c588a9ce7113bdcd776becc587036e6a51261ba7d67008619b9cdf9da478674ba34cc46d255a965a9dc6dbaec83c9092e59daa95a2b49
data/.gitignore CHANGED
@@ -8,6 +8,7 @@ InstalledFiles
8
8
  _yardoc
9
9
  coverage
10
10
  doc/
11
+ html/
11
12
  lib/bundler/man
12
13
  pkg
13
14
  rdoc
data/README.md CHANGED
@@ -8,6 +8,7 @@ This rubygem queries the following Passive DNS databases:
8
8
  * Mnemonic
9
9
  * PassiveDNS.cn (Qihoo 360 Technology Co.,Ltd)
10
10
  * PassiveTotal
11
+ * RiskIQ
11
12
  * TCPIPUtils
12
13
  * VirusTotal
13
14
 
@@ -54,6 +55,9 @@ From version 2.0.0 on, all configuration keys for passive DNS providers are in o
54
55
  [circl]
55
56
  USERNAME = circl_user
56
57
  PASSWORD = circl_pass
58
+ [riskiq]
59
+ API_TOKEN = 0123456789abcdef
60
+ API_PRIVATE_KEY = 01234567890abcdefghijklmnopqrstu
57
61
 
58
62
  CIRCL also can use and authorization token. In that case, you should drop the USERNAME and PASSWORD options and change the section to something like the following:
59
63
 
@@ -67,6 +71,7 @@ CIRCL also can use and authorization token. In that case, you should drop the U
67
71
  * DNSDB (Farsight Security) : https://api.dnsdb.info/
68
72
  * Mnemonic : mss .at. mnemonic.no
69
73
  * PassiveTotal : https://www.passivetotal.org
74
+ * RiskIQ : https://github.com/RiskIQ/python_api/blob/master/LICENSE
70
75
  * TCPIPUtils : http://www.tcpiputils.com/premium-access
71
76
  * VirusTotal : https://www.virustotal.com
72
77
 
@@ -88,6 +93,7 @@ Or use the included tool...
88
93
  -dd use DNSDB
89
94
  -dm use Mnemonic
90
95
  -dp use PassiveTotal
96
+ -dr use RiskIQ
91
97
  -dt use TCPIPUtils
92
98
  -dv use VirusTotal
93
99
  -dvt uses VirusTotal and TCPIPUtils (for example)
data/Rakefile CHANGED
@@ -2,6 +2,7 @@
2
2
  require "bundler/gem_tasks"
3
3
 
4
4
  require 'rake/testtask'
5
+ require 'rdoc/task'
5
6
 
6
7
  Rake::TestTask.new do |t|
7
8
  t.libs << 'lib'
@@ -9,4 +10,11 @@ Rake::TestTask.new do |t|
9
10
  t.verbose = true
10
11
  end
11
12
 
13
+ RDoc::Task.new do |rd|
14
+ rd.main = "README.doc"
15
+ rd.rdoc_files.include("README.md", "lib/**/*.rb")
16
+ rd.options << "--all"
17
+ rd.options << "--verbose"
18
+ end
19
+
12
20
  task :default => :test
@@ -8,27 +8,32 @@ require 'passivedns/client/passivedb'
8
8
 
9
9
  # load all the providers
10
10
  $passivedns_providers = Array.new
11
- provider_path = File.dirname(__FILE__)+"/client/providers/*.rb"
11
+ provider_path = File.dirname(__FILE__)+"/client/provider/*.rb"
12
12
  Dir.glob(provider_path).each do |provider|
13
13
  name = File.basename(provider, '.rb')
14
- require "passivedns/client/providers/#{name}.rb"
14
+ require "passivedns/client/provider/#{name}.rb"
15
15
  $passivedns_providers << name
16
16
  end
17
17
 
18
18
  require 'configparser'
19
19
 
20
- module PassiveDNS
21
-
20
+ module PassiveDNS # :nodoc:
21
+ # struct to contain the results from a PassiveDNS lookup
22
22
  class PDNSResult < Struct.new(:source, :response_time, :query, :answer, :rrtype, :ttl, :firstseen, :lastseen, :count); end
23
23
 
24
+ # coodinates the lookups accross all configured PassiveDNS providers
24
25
  class Client
26
+
27
+ # instantiate and configure all specified PassiveDNS providers
28
+ # pdns array of passivedns provider names, e.g., ["dnsdb","virustotal"]
29
+ # configfile filename of the passivedns-client configuration (this should probably be abstracted)
25
30
  def initialize(pdns=$passivedns_providers, configfile="#{ENV['HOME']}/.passivedns-client")
26
31
  cp = ConfigParser.new(configfile)
27
32
  # this creates a map of all the PassiveDNS provider names and their classes
28
33
  class_map = {}
29
- PassiveDNS.constants.each do |const|
30
- if PassiveDNS.const_get(const).is_a?(Class) and PassiveDNS.const_get(const).superclass == PassiveDNS::PassiveDB
31
- class_map[PassiveDNS.const_get(const).config_section_name] = PassiveDNS.const_get(const)
34
+ PassiveDNS::Provider.constants.each do |const|
35
+ if PassiveDNS::Provider.const_get(const).is_a?(Class) and PassiveDNS::Provider.const_get(const).superclass == PassiveDNS::PassiveDB
36
+ class_map[PassiveDNS::Provider.const_get(const).config_section_name] = PassiveDNS::Provider.const_get(const)
32
37
  end
33
38
  end
34
39
 
@@ -43,12 +48,14 @@ module PassiveDNS
43
48
 
44
49
  end #initialize
45
50
 
51
+ # set the debug flag
46
52
  def debug=(d)
47
53
  @pdnsdbs.each do |pdnsdb|
48
54
  pdnsdb.debug = d
49
55
  end
50
56
  end
51
57
 
58
+ # perform the query lookup accross all configured PassiveDNS providers
52
59
  def query(item, limit=nil)
53
60
  threads = []
54
61
  @pdnsdbs.each do |pdnsdb|
@@ -4,18 +4,39 @@ require 'getoptlong'
4
4
  require 'yaml'
5
5
  require 'pp'
6
6
 
7
- module PassiveDNS
7
+ module PassiveDNS # :nodoc:
8
+ # Handles all the command-line parsing, state tracking, and dispatching queries to the PassiveDNS::Client instance
9
+ # CLInterface is aliased by CLI
8
10
  class CLInterface
11
+ # generates a mapping between the option letter for each PassiveDNS provider and the class
9
12
  def self.get_letter_map
10
13
  letter_map = {}
11
- PassiveDNS.constants.each do |const|
12
- if PassiveDNS.const_get(const).is_a?(Class) and PassiveDNS.const_get(const).superclass == PassiveDNS::PassiveDB
13
- letter_map[PassiveDNS.const_get(const).option_letter] = [PassiveDNS.const_get(const).name, PassiveDNS.const_get(const).config_section_name]
14
+ mod = PassiveDNS::Provider
15
+ mod.constants.each do |const|
16
+ if mod.const_get(const).is_a?(Class) and mod.const_get(const).superclass == PassiveDNS::PassiveDB
17
+ letter = mod.const_get(const).option_letter
18
+ name = mod.const_get(const).name
19
+ config_section_name = mod.const_get(const).config_section_name
20
+ letter_map[letter] = [name, config_section_name]
14
21
  end
15
22
  end
16
23
  letter_map
17
24
  end
18
25
 
26
+ # parses the command line and yields an options hash
27
+ # === Default Options
28
+ # options = {
29
+ # :pdnsdbs => [], # passive dns providers to query
30
+ # :format => "text", # output format
31
+ # :sep => "\t", # field separator for text format
32
+ # :recursedepth => 1, # recursion depth
33
+ # :wait => 0, # wait period between recursions
34
+ # :res => nil, # unused. I don't remember why this is here.
35
+ # :debug => false, # debug flag
36
+ # :sqlitedb => nil, # filename for maintaining state in an sqlite3 db
37
+ # :limit => nil, # number of results per provider per recursion
38
+ # :help => false # display the usage text
39
+ # }
19
40
  def self.parse_command_line(args)
20
41
  origARGV = ARGV.dup
21
42
  ARGV.replace(args)
@@ -132,6 +153,8 @@ module PassiveDNS
132
153
  [options, args]
133
154
  end
134
155
 
156
+ # returns a string containing the usage information
157
+ # takes in a hash of letter to passive dns providers
135
158
  def self.usage(letter_map)
136
159
  databases = letter_map.keys.sort.join("")
137
160
  help_text = ""
@@ -167,6 +190,7 @@ module PassiveDNS
167
190
  help_text
168
191
  end
169
192
 
193
+ # performs a stateful, recursive (if desired) passive DNS lookup against all specified providers
170
194
  def self.pdnslookup(state, pdnsclient, options)
171
195
  recursedepth = options[:recursedepth]
172
196
  wait = options[:wait]
@@ -195,6 +219,7 @@ module PassiveDNS
195
219
  state
196
220
  end
197
221
 
222
+ # returns a string transforming all the PassiveDNS::PDNSResult stored in the state object into text/xml/json/etc.
198
223
  def self.results_to_s(state,options)
199
224
  format = options[:format]
200
225
  sep = options[:sep]
@@ -216,6 +241,7 @@ module PassiveDNS
216
241
  end
217
242
  end
218
243
 
244
+ # create a state instance
219
245
  def self.create_state(sqlitedb=nil)
220
246
  state = nil
221
247
  if sqlitedb
@@ -225,6 +251,7 @@ module PassiveDNS
225
251
  end
226
252
  end
227
253
 
254
+ # main method, takes command-line arguments and performs the desired queries and outputs
228
255
  def self.run(args)
229
256
  options, items = parse_command_line(args)
230
257
  if options[:help]
@@ -240,8 +267,8 @@ module PassiveDNS
240
267
  pdnsclient = PassiveDNS::Client.new(options[:pdnsdbs])
241
268
  pdnsclient.debug = options[:debug]
242
269
 
243
- if ARGV.length > 0
244
- ARGV.each do |arg|
270
+ if items.length > 0
271
+ items.each do |arg|
245
272
  state.add_query(arg,'pending',0)
246
273
  end
247
274
  else
@@ -253,6 +280,7 @@ module PassiveDNS
253
280
  results_to_s(state,options)
254
281
  end
255
282
  end
283
+ # Alias for the CLInterface class
256
284
  CLI = PassiveDNS::CLInterface
257
285
  end
258
286
 
@@ -1,17 +1,22 @@
1
- module PassiveDNS
1
+ module PassiveDNS #:nodoc: don't document this
2
+ # abstract class that all PassiveDNS::Provider should subclass
2
3
  class PassiveDB
4
+ # raises an exception that this should be implemented by the subclass
3
5
  def self.name
4
6
  raise "You should implement your own version of .name"
5
7
  end
6
8
 
9
+ # raises an exception that this should be implemented by the subclass
7
10
  def self.config_section_name
8
11
  name
9
12
  end
10
13
 
14
+ # raises an exception that this should be implemented by the subclass
11
15
  def self.option_letter
12
16
  raise "You should pick a unique letter to serve as your database option letter for the command line option -d"
13
17
  end
14
18
 
19
+ # raises an exception that this should be implemented by the subclass
15
20
  def lookup(label, limit=nil)
16
21
  raise "You must implement the lookup function"
17
22
  end
@@ -0,0 +1,102 @@
1
+ require 'open-uri'
2
+
3
+ module PassiveDNS #:nodoc: don't document this
4
+ # The Provider module contains all the Passive DNS provider client code
5
+ module Provider
6
+
7
+ # Queries BFK.de's passive DNS database
8
+ class BFK < PassiveDB
9
+ # Sets the modules self-reported name to "BFK.de"
10
+ def self.name
11
+ "BFK.de"
12
+ end
13
+ # Sets the configuration section name to "bfk"
14
+ def self.config_section_name
15
+ "bfk"
16
+ end
17
+ # Sets the command line database argument to "b"
18
+ def self.option_letter
19
+ "b"
20
+ end
21
+
22
+ # :debug enables verbose logging to standard output
23
+ attr_accessor :debug
24
+ # === Options
25
+ # * :debug Sets the debug flag for the module
26
+ # * "URL" Alternate url for testing. Defaults to "http://www.bfk.de/bfk_dnslogger.html?query="
27
+ #
28
+ # === Example Instantiation
29
+ #
30
+ # options = {
31
+ # :debug => true,
32
+ # "URL" => "http://www.bfk.de/bfk_dnslogger.html?query="
33
+ # }
34
+ #
35
+ # PassiveDNS::Provider::BFK.new(options)
36
+ #
37
+
38
+ def initialize(options={})
39
+ @debug = options[:debug] || false
40
+ @base = options["URL"] || "http://www.bfk.de/bfk_dnslogger.html?query="
41
+ end
42
+
43
+ # Takes a label (either a domain or an IP address) and returns
44
+ # an array of PassiveDNS::PDNSResult instances with the answers to the query
45
+ def lookup(label, limit=nil)
46
+ $stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
47
+ Timeout::timeout(240) {
48
+ t1 = Time.now
49
+ open(
50
+ @base+label,
51
+ "User-Agent" => "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}"
52
+ ) do |f|
53
+ t2 = Time.now
54
+ recs = parse(f.read,t2-t1)
55
+ if limit
56
+ recs[0,limit]
57
+ else
58
+ recs
59
+ end
60
+ end
61
+ }
62
+ rescue Timeout::Error => e
63
+ $stderr.puts "#{self.class.name} lookup timed out: #{label}"
64
+ end
65
+
66
+ private
67
+
68
+ # parses the webpage returned by BFK to generate an array of PDNSResult
69
+ def parse(page,response_time)
70
+ line = page.unpack('C*').pack('U*').split(/<table/).grep(/ id=\"logger\"/)
71
+ return [] unless line.length > 0
72
+ line = line[0].gsub(/[\t\n]/,'').gsub(/<\/table.*/,'')
73
+ rows = line.split(/<tr.*?>/)
74
+ res = []
75
+ rows.collect do |row|
76
+ r = row.split(/<td>/).map{|x| x.gsub(/<.*?>/,'').gsub(/\&.*?;/,'')}[1,1000]
77
+ if r and r[0] =~ /\w/
78
+ # BFK includes the MX weight in the answer response. First, find the MX records, then dump the weight to present a consistent record name to the collecting array. Otherwise the other repositories will present the same answer and your results will become cluttered with duplicates.
79
+ if r[1] == "MX" then
80
+ # MX lines look like "5 mx.domain.tld", so split on the space and assign r[2] (:answer) to the latter part.
81
+ #s = r[2].split(/\w/).map{|x| x}[1,1000]
82
+ # r[2] = s[1]
83
+ r[2] =~ /[0-9]+?\s(.+)/
84
+ s=$1
85
+ puts "DEBUG: == BFK: MX Parsing Strip: Answer: " + r[2] + " : mod: " + s if @debug
86
+ r[2] = s
87
+
88
+ ######### FIX BLANKS FOR MX
89
+
90
+ end
91
+ res << PDNSResult.new(self.class.name,response_time,r[0],r[2],r[1])
92
+ end
93
+ end
94
+ res
95
+ rescue Exception => e
96
+ $stderr.puts "#{self.class.name} Exception: #{e}"
97
+ raise e
98
+ end
99
+
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,111 @@
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 CIRCL.LU's passive DNS database
11
+ # Circl is aliased by CIRCL
12
+ class Circl < PassiveDB
13
+ # Sets the modules self-reported name to "CIRCL"
14
+ def self.name
15
+ "CIRCL"
16
+ end
17
+ # Sets the configuration section name to "circl"
18
+ def self.config_section_name
19
+ "circl"
20
+ end
21
+ # Sets the command line database argument to "c"
22
+ def self.option_letter
23
+ "c"
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
+ # * "USERNAME" User name associated with your CIRCL account
31
+ # * "PASSWORD" Password associated with your CIRCL account
32
+ # * "AUTH_TOKEN" Authorization token associated with your CIRCL account
33
+ # * "URL" Alternate url for testing. Defaults to "https://www.circl.lu/pdns/query"
34
+
35
+ # You should either have a username+password or an authorization token to use this service
36
+ #
37
+ # === Example Instantiation
38
+ #
39
+ # options = {
40
+ # :debug => true,
41
+ # "USERNAME" => "circl_user",
42
+ # "PASSWORD" => "circl_pass",
43
+ # "URL" => "https://www.circl.lu/pdns/query"
44
+ # }
45
+ #
46
+ # PassiveDNS::Provider::CIRCL.new(options)
47
+ #
48
+ def initialize(options={})
49
+ @debug = options[:debug] || false
50
+ @username = options["USERNAME"]
51
+ @password = options["PASSWORD"]
52
+ @auth_token = options["AUTH_TOKEN"]
53
+ @url = options["URL"] || "https://www.circl.lu/pdns/query"
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 = @url+"/"+label
62
+ $stderr.puts "DEBUG: #{self.class.name} url = #{url}" if @debug
63
+ url = URI.parse url
64
+ http = Net::HTTP.new(url.host, url.port)
65
+ http.use_ssl = (url.scheme == 'https')
66
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
67
+ http.verify_depth = 5
68
+ request = Net::HTTP::Get.new(url.request_uri)
69
+ request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
70
+ if @username
71
+ request.basic_auth(@username, @password)
72
+ end
73
+ if @auth_token
74
+ request.add_field("Authorization", @auth_token)
75
+ end
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
+ rescue Timeout::Error => e
87
+ $stderr.puts "#{self.class.name} lookup timed out: #{label}"
88
+ end
89
+
90
+ private
91
+
92
+ # parses the response of circl's JSON reply to generate an array of PDNSResult
93
+ def parse_json(page,query,response_time=0)
94
+ res = []
95
+ # need to remove the json_class tag or the parser will crap itself trying to find a class to align it to
96
+ page.split(/\n/).each do |line|
97
+ row = JSON.parse(line)
98
+ res << PDNSResult.new(self.class.name,response_time,
99
+ row['rrname'], row['rdata'], row['rrtype'], 0,
100
+ row['time_first'], row['time_last'], row['count'])
101
+ end
102
+ res
103
+ rescue Exception => e
104
+ $stderr.puts "#{self.class.name} Exception: #{e}"
105
+ raise e
106
+ end
107
+
108
+ end
109
+ CIRCL = PassiveDNS::Provider::Circl
110
+ end
111
+ end