passive-dns 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data.tar.gz.sig ADDED
Binary file
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Chris Lee, PhD
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = passive-dns
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to passive-dns
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 Chris Lee, PhD. See LICENSE.txt for
18
+ further details.
19
+
data/bin/pdnstool ADDED
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'passive-dns'
4
+ require 'yaml'
5
+
6
+ #if __FILE__ == $0
7
+ def pdnslookup(state,pdnsdbs,recursedepth=1,wait=0,debug=false)
8
+ puts "pdnslookup: #{state.level} #{recursedepth}" if debug
9
+ level = 0
10
+ while level < recursedepth
11
+ puts "pdnslookup: #{level} < #{recursedepth}" if debug
12
+ state.each_query(recursedepth) do |q|
13
+ pdnsdbs.each do |pdnsdb|
14
+ rv = pdnsdb.lookup(q)
15
+ if rv
16
+ rv.each do |r|
17
+ if ["A","AAAA","NS","CNAME","PTR"].index(r.rrtype)
18
+ puts "pdnslookup: #{r.to_s}" if debug
19
+ state.add_result(r)
20
+ end
21
+ end
22
+ else
23
+ state.update_query(rv,'failed')
24
+ end
25
+ end
26
+ sleep wait if level < recursedepth
27
+ end
28
+ level += 1
29
+ end
30
+ state
31
+ end
32
+
33
+ def printresults(state,format,sep="\t")
34
+ case format
35
+ when 'text'
36
+ puts state.to_s(sep)
37
+ when 'yaml'
38
+ puts state.to_yaml
39
+ when 'xml'
40
+ puts state.to_xml
41
+ when 'json'
42
+ puts state.to_json
43
+ when 'gdf'
44
+ puts state.to_gdf
45
+ when 'graphviz'
46
+ puts state.to_graphviz
47
+ when 'graphml'
48
+ puts state.to_graphml
49
+ end
50
+ end
51
+
52
+ def usage
53
+ puts "Usage: #{$0} [-a|-b|-d|-i|-e] [-c|-x|-y|-j|-t] [-s <sep>] [-f <file>] [-r#|-w#|-l] <ip|domain|cidr>"
54
+ puts " -a uses all of the available passive dns databases"
55
+ puts " -b only use BFK"
56
+ puts " -d only use DNSParse (default)"
57
+ puts " -i only use ISC"
58
+ puts " -e only use CERT-EE"
59
+ puts " -g outputs a link-nodal GDF visualization definition"
60
+ puts " -v outputs a link-nodal graphviz visualization definition"
61
+ puts " -m output a link-nodal graphml visualization definition"
62
+ puts " -c outputs CSV"
63
+ puts " -x outputs XML"
64
+ puts " -y outputs YAML"
65
+ puts " -j outputs JSON"
66
+ puts " -t outputs ASCII text (default)"
67
+ puts " -s <sep> specifies a field separator for text output, default is tab"
68
+ puts " -f[file] specifies a sqlite3 database used to read the current state - useful for large result sets and generating graphs of previous runs."
69
+ puts " -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!"
70
+ puts " -w# specifies the amount of time to wait, in seconds, between queries (Default: 0)"
71
+ puts " -l outputs debugging information"
72
+ exit
73
+ end
74
+
75
+ opts = GetoptLong.new(
76
+ [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
77
+ [ '--debug', '-l', GetoptLong::NO_ARGUMENT ],
78
+ [ '--all', '-a', GetoptLong::NO_ARGUMENT ],
79
+ [ '--bfk', '-b', GetoptLong::NO_ARGUMENT ],
80
+ [ '--dnsparse', '-d', GetoptLong::NO_ARGUMENT ],
81
+ [ '--isc', '-i', GetoptLong::NO_ARGUMENT ],
82
+ [ '--certee', '-e', GetoptLong::NO_ARGUMENT ],
83
+ [ '--gdf', '-g', GetoptLong::NO_ARGUMENT ],
84
+ [ '--graphviz', '-v', GetoptLong::NO_ARGUMENT ],
85
+ [ '--graphml', '-m', GetoptLong::NO_ARGUMENT ],
86
+ [ '--csv', '-c', GetoptLong::NO_ARGUMENT ],
87
+ [ '--xml', '-x', GetoptLong::NO_ARGUMENT ],
88
+ [ '--yaml', '-y', GetoptLong::NO_ARGUMENT ],
89
+ [ '--json', '-j', GetoptLong::NO_ARGUMENT ],
90
+ [ '--text', '-t', GetoptLong::NO_ARGUMENT ],
91
+ [ '--sep', '-s', GetoptLong::REQUIRED_ARGUMENT ],
92
+ [ '--sqlite3', '-f', GetoptLong::REQUIRED_ARGUMENT ],
93
+ [ '--recurse', '-r', GetoptLong::REQUIRED_ARGUMENT ],
94
+ [ '--wait', '-w', GetoptLong::REQUIRED_ARGUMENT ]
95
+ )
96
+
97
+ # sets the default search methods
98
+ pdnsdbs = []
99
+ format = "text"
100
+ sep = "\t"
101
+ recursedepth = 1
102
+ wait = 0
103
+ res = nil
104
+ debug = false
105
+ sqlitedb = nil
106
+
107
+ opts.each do |opt, arg|
108
+ case opt
109
+ when '--help'
110
+ usage
111
+ when '--debug'
112
+ debug = true
113
+ $stderr.puts "Using proxy settings: http_proxy=#{ENV['http_proxy']}, https_proxy=#{ENV['https_proxy']}"
114
+ when '--all'
115
+ pdnsdbs << PassiveDNS::BFKClient.new
116
+ pdnsdbs << PassiveDNS::DNSParse.new
117
+ pdnsdbs << PassiveDNS::ISC.new
118
+ pdnsdbs << PassiveDNS::CERTEE.new
119
+ when '--bfk'
120
+ pdnsdbs << PassiveDNS::BFKClient.new
121
+ when '--dnsparse'
122
+ pdnsdbs << PassiveDNS::DNSParse.new
123
+ when '--isc'
124
+ pdnsdbs << PassiveDNS::ISC.new
125
+ when '--certee'
126
+ pdnsdbs << PassiveDNS::CERTEE.new
127
+ when '--gdf'
128
+ format = 'gdf'
129
+ when '--graphviz'
130
+ format = 'graphviz'
131
+ when '--graphml'
132
+ format = 'graphml'
133
+ when '--csv'
134
+ format = 'text'
135
+ sep = ','
136
+ when '--yaml'
137
+ format = 'yaml'
138
+ when '--xml'
139
+ format = 'xml'
140
+ when '--json'
141
+ format = 'json'
142
+ when '--text'
143
+ format = 'text'
144
+ when '--sep'
145
+ sep = arg
146
+ when '--recurse'
147
+ recursedepth = arg.to_i
148
+ if recursedepth > 3
149
+ $stderr.puts "WARNING: a recursedepth of > 3 can be abusive, please reconsider: sleeping 60 seconds for sense to come to you (hint: hit CTRL-C)"
150
+ sleep 60
151
+ end
152
+ when '--wait'
153
+ wait = arg.to_i
154
+ when '--sqlite3'
155
+ sqlitedb = arg
156
+ else
157
+ usage
158
+ end
159
+ end
160
+
161
+ if pdnsdbs.length == 0
162
+ pdnsdbs << PassiveDNS::DNSParse.new
163
+ end
164
+
165
+ pdnsdbs.each do |pdnsdb|
166
+ pdnsdb.debug = true if debug
167
+ if pdnsdb.class.to_s == "PassiveDNS::BFKClient" and recursedepth > 1 and wait < 60
168
+ wait = 60
169
+ $stderr.puts "Enforcing a minimal 60 second wait when using BFK for recursive crawling"
170
+ end
171
+ end
172
+
173
+ state = nil
174
+ if sqlitedb
175
+ state = PassiveDNS::PDNSToolStateDB.new(sqlitedb)
176
+ else
177
+ state = PassiveDNS::PDNSToolState.new
178
+ end
179
+ state.debug = true if debug
180
+
181
+ if ARGV.length > 0
182
+ ARGV.each do |arg|
183
+ state.add_query(arg,'pending',0)
184
+ end
185
+ else
186
+ $stdin.each_line do |l|
187
+ state.add_query(l.chomp,'pending',0)
188
+ end
189
+ end
190
+ pdnslookup(state,pdnsdbs,recursedepth,wait,debug)
191
+ printresults(state,format,sep)
192
+ #end
@@ -0,0 +1,5 @@
1
+ require 'pdns/pdns'
2
+ require 'pdns/dnsparse'
3
+ require 'pdns/iscpdns'
4
+ require 'pdns/bfkclient'
5
+ require 'pdns/certeepdns'
@@ -0,0 +1,58 @@
1
+ require 'open-uri'
2
+
3
+ module PassiveDNS
4
+ class BFKClient
5
+ attr_accessor :debug
6
+ def initialize
7
+ @debug = false
8
+ end
9
+ @@base = "http://www.bfk.de/bfk_dnslogger.html?query="
10
+ def parse(page,response_time)
11
+ line = page.split(/<table/).grep(/ id=\"logger\"/)
12
+ return [] unless line.length > 0
13
+ line = line[0].gsub(/[\t\n]/,'').gsub(/<\/table.*/,'')
14
+ rows = line.split(/<tr.*?>/)
15
+ res = []
16
+ rows.collect do |row|
17
+ r = row.split(/<td>/).map{|x| x.gsub(/<.*?>/,'').gsub(/\&.*?;/,'')}[1,1000]
18
+ if r and r[0] =~ /\w/
19
+ # 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.
20
+ if r[1] == "MX" then
21
+ # MX lines look like "5 mx.domain.tld", so split on the space and assign r[2] (:answer) to the latter part.
22
+ #s = r[2].split(/\w/).map{|x| x}[1,1000]
23
+ # r[2] = s[1]
24
+ r[2] =~ /[0-9]+?\s(.+)/
25
+ s=$1
26
+ puts "DEBUG: == BFK: MX Parsing Strip: Answer: " + r[2] + " : mod: " + s if @debug
27
+ r[2] = s
28
+
29
+ ######### FIX BLANKS FOR MX
30
+
31
+ end
32
+ res << PDNSResult.new('BFK',response_time,r[0],r[2],r[1],nil,nil,nil)
33
+ end
34
+ end
35
+ res
36
+ rescue Exception => e
37
+ $stderr.puts "BFKClient Exception: #{e}"
38
+ raise e
39
+ end
40
+
41
+ def lookup(label)
42
+ $stderr.puts "DEBUG: BFKClient.lookup(#{label})" if @debug
43
+ Timeout::timeout(240) {
44
+ t1 = Time.now
45
+ open(
46
+ @@base+label,
47
+ "User-Agent" => "Ruby/#{RUBY_VERSION} ChrisLee passive dns script",
48
+ :http_basic_authentication => [@user,@pass]
49
+ ) do |f|
50
+ t2 = Time.now
51
+ parse(f.read,t2-t1)
52
+ end
53
+ }
54
+ rescue Timeout::Error => e
55
+ $stderr.puts "BFK lookup timed out: #{label}"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,36 @@
1
+ require 'socket'
2
+
3
+ module PassiveDNS
4
+ class CERTEE
5
+ @@host = "sim.cert.ee"
6
+ attr_accessor :debug
7
+ def initialize
8
+ end
9
+ def lookup(label)
10
+ $stderr.puts "DEBUG: CERTEE.lookup(#{label})" if @debug
11
+ recs = []
12
+ begin
13
+ t1 = Time.now
14
+ s = TCPSocket.new(@@host,43)
15
+ s.puts(label)
16
+ s.each_line do |l|
17
+ (lbl,ans,fs,ls) = l.chomp.split(/\t/)
18
+ rrtype = 'A'
19
+ if ans =~ /^\d+\.\d+\.\d+\.\d+$/
20
+ rrtype = 'A'
21
+ elsif ans =~ /^ns/
22
+ rrtype = 'NS'
23
+ else
24
+ rrtype = 'CNAME'
25
+ end
26
+ t2 = Time.now
27
+ recs << PDNSResult.new('CERTEE',t2-t1,lbl,ans,rrtype,0,Time.parse(fs).utc.strftime("%Y-%m-%dT%H:%M:%SZ"),Time.parse(ls).utc.strftime("%Y-%m-%dT%H:%M:%SZ"))
28
+ end
29
+ rescue SocketError => e
30
+ $stderr.puts e
31
+ end
32
+ return nil unless recs.length > 0
33
+ recs
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,88 @@
1
+ # DESCRIPTION: this is a module for pdns.rb, primarily used by pdnstool.rb, to query Bojan Zdrnja's passive DNS database, DNSParse
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'openssl'
5
+
6
+ module PassiveDNS
7
+ class DNSParse
8
+ attr_accessor :debug
9
+ @@dns_rtypes = {1 => 'A', 2 => 'NS', 3 => 'MD', 4 => 'MF', 5 => 'CNAME', 6 => 'SOA', 7 => 'MB', 8 => 'MG', 9 => 'MR', 10 => 'NULL', 11 => 'WKS', 12 => 'PTR', 13 => 'HINFO', 14 => 'MINFO', 15 => 'MX', 16 => 'TXT', 17 => 'RP', 18 => 'AFSDB', 19 => 'X25', 20 => 'ISDN', 21 => 'RT', 22 => 'NSAP', 23 => 'NSAP-PTR', 24 => 'SIG', 25 => 'KEY', 26 => 'PX', 27 => 'GPOS', 28 => 'AAAA', 29 => 'LOC', 30 => 'NXT', 31 => 'EID', 32 => 'NIMLOC', 33 => 'SRV', 34 => 'ATMA', 35 => 'NAPTR', 36 => 'KX', 37 => 'CERT', 38 => 'A6', 39 => 'DNAME', 40 => 'SINK', 41 => 'OPT', 42 => 'APL', 43 => 'DS', 44 => 'SSHFP', 45 => 'IPSECKEY', 46 => 'RRSIG', 47 => 'NSEC', 48 => 'DNSKEY', 49 => 'DHCID', 55 => 'HIP', 99 => 'SPF', 100 => 'UINFO', 101 => 'UID', 102 => 'GID', 103 => 'UNSPEC', 249 => 'TKEY', 250 => 'TSIG', 251 => 'IXFR', 252 => 'AXFR', 253 => 'MAILB', 254 => 'MAILA', 255 => 'ALL'}
10
+ def initialize(config="#{ENV['HOME']}/.dnsparse")
11
+ if File.exist?(config)
12
+ @base,@user,@pass = File.open(config).read.split(/\n/)
13
+ $stderr.puts "DEBUG: DNSParse#initialize(#{@base}, #{@user}, #{@pass})" if @debug
14
+ else
15
+ raise "Configuration file for DNSParse is required for intialization\nFormat of configuration file (default: #{ENV['HOME']}/.dnsparse) is:\n<url>\n<username>\n<password>\n"
16
+ end
17
+ end
18
+
19
+ def parse_json(page,response_time=0)
20
+ res = []
21
+ # need to remove the json_class tag or the parser will crap itself trying to find a class to align it to
22
+ page = page.gsub(/\"json_class\"\:\"PDNSResult\"\,/,'')
23
+ recs = JSON.parse(page)
24
+ recs.each do |row|
25
+ res << PDNSResult.new('DNSParse',response_time,row["query"],row["answer"],@@dns_rtypes[row["rrtype"].to_i],row["ttl"],row["firstseen"],row["lastseen"])
26
+ end
27
+ res
28
+ rescue Exception => e
29
+ $stderr.puts "DNSParse Exception: #{e}"
30
+ raise e
31
+ end
32
+
33
+ def parse_html(page,response_time=0)
34
+ rows = []
35
+ line = page.split(/<table/).grep(/ id=\"dnsparse\"/)
36
+ return [] unless line.length > 0
37
+ line = line[0].gsub(/[\t\n]/,'').gsub(/<\/table.*/,'')
38
+ rows = line.split(/<tr.*?>/)
39
+ res = []
40
+ rows.collect do |row|
41
+ r = row.split(/<td>/).map{|x| x.gsub(/<.*?>/,'').gsub(/\&.*?;/,'').gsub(/[\t\n]/,'')}[1,1000]
42
+ if r and r[0] =~ /\w/
43
+ #TXT records screw up other functions and don't provide much without a lot of subparshing. Dropping for now.
44
+ if r[2]!="TXT" then
45
+ res << PDNSResult.new('DNSParse',response_time,r[0],r[1],r[2],r[3],r[4],r[5])
46
+ end
47
+ end
48
+ end
49
+ res
50
+ rescue Exception => e
51
+ $stderr.puts "DNSParse Exception: #{e}"
52
+ raise e
53
+ end
54
+
55
+ def lookup(label)
56
+ $stderr.puts "DEBUG: DNSParse.lookup(#{label})" if @debug
57
+ Timeout::timeout(240) {
58
+ year = Time.now.strftime("%Y")
59
+ url = "#{@base}#{label}&year=#{year.to_i - 1}"
60
+ if label =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/
61
+ url.gsub!(/query\.php/,'cidr.php')
62
+ elsif label =~ /\*$/
63
+ url.gsub!(/query\.php/,'wildcard.php')
64
+ end
65
+ $stderr.puts "DEBUG: DNSParse url = #{url}" if @debug
66
+ url = URI.parse url
67
+ http = Net::HTTP.new(url.host, url.port)
68
+ http.use_ssl = (url.scheme == 'https')
69
+ http.ca_file = "#{ENV['HOME']}/bin/data/cacert.pem"
70
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
71
+ http.verify_depth = 5
72
+ request = Net::HTTP::Get.new(url.path+"?"+url.query)
73
+ request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} ChrisLee passive dns script")
74
+ request.basic_auth @user, @pass
75
+ t1 = Time.now
76
+ response = http.request(request)
77
+ t2 = Time.now
78
+ if @base =~ /format=json/
79
+ parse_json(response.body,t2-t1)
80
+ else
81
+ parse_html(response.body,t2-t1)
82
+ end
83
+ }
84
+ rescue Timeout::Error => e
85
+ $stderr.puts "DNSParse lookup timed out: #{label}"
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,79 @@
1
+ # DESCRIPTION: this is a module for pdns.rb, primarily used by pdnstool.rb, to query the ISC passive DNS database
2
+ # details on the API are at https://dnsdb.isc.org/doc/isc-dnsdb-api.html
3
+ # to request an API key, please email dnsdb@isc.org
4
+ require 'net/http'
5
+ require 'net/https'
6
+
7
+ module PassiveDNS
8
+ class ISC
9
+ attr_accessor :debug
10
+ @@base="https://dnsdb-api.isc.org/lookup"
11
+
12
+ def initialize(config="#{ENV['HOME']}/.isc-dnsdb-query.conf")
13
+ @debug = false
14
+ if File.exist?(config)
15
+ @key = File.open(config).readline.chomp
16
+ if @key =~ /^[0-9a-f]{64}$/
17
+ # pass
18
+ elsif @key =~ /^APIKEY=\"([0-9a-f]{64})\"/
19
+ @key = $1
20
+ else
21
+ raise "Format of configuration file (default: #{ENV['HOME']}/.isc-dnsdb-query.conf) is:\nAPIKEY=\"<key>\"\nE.g.,\nAPIKEY=\"d41d8cd98f00b204e9800998ecf8427ed41d8cd98f00b204e9800998ecf8427e\"\n"
22
+ end
23
+ else
24
+ raise "Configuration file for ISC is required for intialization\nFormat of configuration file (default: #{ENV['HOME']}/.isc-dnsdb-query.conf) is:\nAPIKEY=\"<key>\"\nE.g.,\nAPIKEY=\"d41d8cd98f00b204e9800998ecf8427ed41d8cd98f00b204e9800998ecf8427e\"\n"
25
+ end
26
+ end
27
+
28
+ def parse_json(page,response_time)
29
+ res = []
30
+ raise "Error: unable to parse request" if page =~ /Error: unable to parse request/
31
+ # need to remove the json_class tag or the parser will crap itself trying to find a class to align it to
32
+ rows = page.split(/\n/)
33
+ rows.each do |row|
34
+ record = JSON.parse(row)
35
+ record['rdata'] = [record['rdata']] if record['rdata'].class == String
36
+ record['rdata'].each do |rdata|
37
+ if record['time_first']
38
+ res << PDNSResult.new('ISC',response_time,record['rrname'],rdata,record['rrtype'],0,Time.at(record['time_first'].to_i).utc.strftime("%Y-%m-%dT%H:%M:%SZ"),Time.at(record['time_last'].to_i).utc.strftime("%Y-%m-%dT%H:%M:%SZ"))
39
+ else
40
+ res << PDNSResult.new('ISC',response_time,record['rrname'],rdata,record['rrtype'],nil,nil,nil)
41
+ end
42
+ end
43
+ end
44
+ res
45
+ rescue Exception => e
46
+ $stderr.puts "ISC Exception: #{e}"
47
+ $stderr.puts page
48
+ raise e
49
+ end
50
+
51
+ def lookup(label)
52
+ $stderr.puts "DEBUG: ISC.lookup(#{label})" if @debug
53
+ Timeout::timeout(240) {
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
66
+ request = Net::HTTP::Get.new(url.path)
67
+ request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} ChrisLee passive dns script")
68
+ request.add_field("X-API-Key", @key)
69
+ request.add_field("Accept", "application/json")
70
+ t1 = Time.now
71
+ response = http.request(request)
72
+ t2 = Time.now
73
+ parse_json(response.body,t2-t1)
74
+ }
75
+ rescue Timeout::Error => e
76
+ $stderr.puts "ISC lookup timed out: #{label}"
77
+ end
78
+ end
79
+ end
data/lib/pdns/pdns.rb ADDED
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env ruby
2
+ # DESCRIPTION: queries passive DNS databases
3
+ # This code is released under the LGPL: http://www.gnu.org/licenses/lgpl-3.0.txt
4
+ # Please note that use of any passive dns database is subject to the terms of use of that passive dns database. Use of this script in violation of their terms is not encouraged in any way. Also, please do not add any obfuscation to try to work around their terms of service. If you need special services, ask the providers for help/permission.
5
+ # Remember, these passive DNS operators are my friends. I don't want to have a row with them because some asshat used this library to abuse them.
6
+
7
+ require 'timeout'
8
+ require 'yaml'
9
+ require 'util/structformatter'
10
+ require 'getoptlong'
11
+ require 'sqlite3'
12
+
13
+ module PassiveDNS
14
+ class PDNSResult < Struct.new(:source, :response_time, :query, :answer, :rrtype, :ttl, :firstseen, :lastseen); end
15
+ class PDNSQueueEntry < Struct.new(:query, :state, :level); end
16
+ class PDNSToolState
17
+ attr_accessor :debug
18
+ attr_reader :level
19
+
20
+ def initialize
21
+ @queue = []
22
+ @recs = []
23
+ @level = 0
24
+ end
25
+
26
+ def next_result
27
+ @recs.each do |rec|
28
+ yield rec
29
+ end
30
+ end
31
+
32
+ def add_result(res)
33
+ @recs << res
34
+ add_query(res.answer,'pending')
35
+ add_query(res.query,'pending')
36
+ end
37
+
38
+ def update_query(query,state)
39
+ @queue.each do |q|
40
+ if q.query == query
41
+ puts "update_query: #{query} (#{q.state}) -> (#{state})" if @debug
42
+ q.state = state
43
+ break
44
+ end
45
+ end
46
+ end
47
+
48
+ def get_state(query)
49
+ @queue.each do |q|
50
+ if q.query == query
51
+ return q.state
52
+ end
53
+ end
54
+ false
55
+ end
56
+
57
+ def add_query(query,state,level=@level+1)
58
+ if query =~ /^\d+ \w+\./
59
+ query = query.split(/ /,2)[1]
60
+ end
61
+ return if get_state(query)
62
+ puts "Adding query: #{query}, #{state}, #{level}" if @debug
63
+ @queue << PDNSQueueEntry.new(query,state,level)
64
+ end
65
+
66
+ def each_query(max_level=20)
67
+ @queue.each do |q|
68
+ if q.state == 'pending' or q.state == 'failed'
69
+ @level = q.level
70
+ q.state = 'queried'
71
+ if q.level < max_level
72
+ yield q.query
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ def to_gdf
79
+ output = "nodedef> name,description VARCHAR(12),color,style\n"
80
+ # IP "$node2,,white,1"
81
+ # domain "$node2,,gray,2"
82
+ # Struct.new(:query, :answer, :rrtype, :ttl, :firstseen, :lastseen)
83
+ colors = {"MX" => "green", "A" => "blue", "CNAME" => "pink", "NS" => "red", "SOA" => "white", "PTR" => "purple", "TXT" => "brown"}
84
+ nodes = {}
85
+ edges = {}
86
+ next_result do |i|
87
+ if i
88
+ nodes[i.query + ",,gray,2"] = true
89
+ if i.answer =~ /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ then
90
+ nodes[i.answer + ",,white,1"] = true
91
+ else
92
+ nodes[i.answer + ",,gray,2"] = true
93
+ end
94
+ color = colors[i.rrtype]
95
+ color ||= "blue"
96
+ edges[i.query + "," + i.answer + "," + color] = true
97
+ end
98
+ end
99
+ nodes.each do |i,j|
100
+ output += i+"\n"
101
+ end
102
+ output += "edgedef> node1,node2,color\n"
103
+ edges.each do |i,j|
104
+ output += i+"\n"
105
+ end
106
+ output
107
+ end
108
+
109
+ def to_graphviz
110
+ colors = {"MX" => "green", "A" => "blue", "CNAME" => "pink", "NS" => "red", "SOA" => "white", "PTR" => "purple", "TXT" => "brown"}
111
+ output = "graph pdns {\n"
112
+ nodes = {}
113
+ next_result do |l|
114
+ if l
115
+ unless nodes[l.query]
116
+ output += " \"#{l.query}\" [shape=ellipse, style=filled, color=gray];\n"
117
+ if l.answer =~ /^\d{3}\.\d{3}\.\d{3}\.\d{3}$/
118
+ output += " \"#{l.answer}\" [shape=box, style=filled, color=white];\n"
119
+ else
120
+ output += " \"#{l.answer}\" [shape=ellipse, style=filled, color=gray];\n"
121
+ end
122
+ nodes[l.query] = true
123
+ end
124
+ output += " \"#{l.query}\" -- \"#{l.answer}\" [color=#{colors[l.rrtype]}];\n"
125
+ end
126
+ end
127
+ output += "}\n"
128
+ end
129
+
130
+ def to_graphml
131
+ output = '<?xml version="1.0" encoding="UTF-8"?>
132
+ <graphml xmlns="http://graphml.graphdrawing.org/xmlns"
133
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
134
+ xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
135
+ http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
136
+ <graph id="G" edgedefault="directed">
137
+ '
138
+ nodes = {}
139
+ edges = {}
140
+ next_result do |r|
141
+ if r
142
+ output += " <node id='#{r.query}'/>\n" unless nodes["#{r.query}"]
143
+ nodes[r.query] = true
144
+ output += " <node id='#{r.answer}'/>\n" unless nodes["#{r.answer}"]
145
+ nodes[r.answer] = true
146
+ output += " <edge source='#{r.query}' target='#{r.answer}'/>\n" unless edges["#{r.query}|#{r.answer}"]
147
+ end
148
+ end
149
+ output += '</graph></graphml>'+"\n"
150
+ end
151
+
152
+ def to_xml
153
+ output = '<?xml version="1.0" encoding="UTF-8" ?>'+"\n"
154
+ output += "<report>\n"
155
+ output += " <results>\n"
156
+ next_result do |rec|
157
+ output += " "+rec.to_xml+"\n"
158
+ end
159
+ output += " </results>\n"
160
+ output += "</report>\n"
161
+ end
162
+
163
+ def to_yaml
164
+ output = ""
165
+ next_result do |rec|
166
+ output += rec.to_yaml+"\n"
167
+ end
168
+ output
169
+ end
170
+
171
+ def to_json
172
+ output = "[\n"
173
+ sep = ""
174
+ next_result do |rec|
175
+ output += sep
176
+ output += rec.to_json
177
+ sep = ",\n"
178
+ end
179
+ output += "\n]\n"
180
+ end
181
+
182
+ def to_s(sep="\t")
183
+ output = ""
184
+ next_result do |rec|
185
+ output += rec.to_s(sep)+"\n"
186
+ end
187
+ output
188
+ end
189
+ end # class PDNSToolState
190
+
191
+
192
+ class PDNSToolStateDB < PDNSToolState
193
+ attr_reader :level
194
+ def initialize(sqlitedb=nil)
195
+ puts "PDNSToolState initialize #{sqlitedb}" if @debug
196
+ @level = 0
197
+ @sqlitedb = sqlitedb
198
+ raise "Cannot use this class without a database file" unless @sqlitedb
199
+ unless File.exists?(@sqlitedb)
200
+ newdb = true
201
+ end
202
+ @sqlitedbh = SQLite3::Database.new(@sqlitedb)
203
+ if newdb
204
+ create_tables
205
+ end
206
+ res = @sqlitedbh.execute("select min(level) from queue where state = 'pending'")
207
+ if res
208
+ res.each do |row|
209
+ @level = row[0].to_i
210
+ puts "changed @level = #{@level}" if @debug
211
+ end
212
+ end
213
+ end
214
+
215
+ def create_tables
216
+ puts "creating tables" if @debug
217
+ @sqlitedbh.execute("create table results (query, answer, rrtype, ttl, firstseen, lastseen, ts REAL)")
218
+ @sqlitedbh.execute("create table queue (query, state, level INTEGER, ts REAL)")
219
+ @sqlitedbh.execute("create index residx on results (ts)")
220
+ @sqlitedbh.execute("create unique index queue_unique on queue (query)")
221
+ @sqlitedbh.execute("create index queue_level_idx on queue (level)")
222
+ @sqlitedbh.execute("create index queue_state_idx on queue (state)")
223
+ end
224
+
225
+ def next_result
226
+ rows = @sqlitedbh.execute("select query, answer, rrtype, ttl, firstseen, lastseen from results order by ts")
227
+ rows.each do |row|
228
+ yield PDNSResult.new(*row)
229
+ end
230
+ end
231
+
232
+ def add_result(res)
233
+ puts "adding result: #{res.to_s}" if @debug
234
+ curtime = Time.now().to_f
235
+ @sqlitedbh.execute("insert into results values ('#{res.query}','#{res.answer}','#{res.rrtype}','#{res.ttl}','#{res.firstseen}','#{res.lastseen}',#{curtime})")
236
+
237
+ add_query(res.answer,'pending')
238
+ add_query(res.query,'pending')
239
+ end
240
+
241
+ def add_query(query,state,level=@level+1)
242
+ return if get_state(query)
243
+ curtime = Time.now().to_f
244
+ begin
245
+ puts "add_query(#{query},#{state},level=#{level})" if @debug
246
+ @sqlitedbh.execute("insert into queue values ('#{query}','#{state}',#{level},#{curtime})")
247
+ rescue
248
+ end
249
+ end
250
+
251
+ def update_query(query,state)
252
+ @sqlitedbh.execute("update queue set state = '#{state}' where query = '#{query}'")
253
+ end
254
+
255
+ def get_state(query)
256
+ rows = @sqlitedbh.execute("select state from queue where query = '#{query}'")
257
+ if rows
258
+ rows.each do |row|
259
+ return row[0]
260
+ end
261
+ end
262
+ false
263
+ end
264
+
265
+ def each_query(max_level=20)
266
+ puts "each_query max_level=#{max_level} curlevel=#{@level}" if @debug
267
+ rows = @sqlitedbh.execute("select query, state, level from queue where state = 'failed' or state = 'pending' order by level limit 1")
268
+ if rows
269
+ rows.each do |row|
270
+ query,state,level = row
271
+ puts " #{query},#{state},#{level}" if @debug
272
+ if level < max_level
273
+ update_query(query,'queried')
274
+ yield query
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end # class PDNSToolStateDB
280
+ end
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env ruby
2
+ # DESCRIPTION: extends ruby Structs to be outputted as yaml, xml, and json. This is meant to be used as a mixin
3
+ require 'json'
4
+
5
+ class Array
6
+ def render_xml(element_name, element)
7
+ str = ""
8
+ if element.class == Date
9
+ str = "<#{element_name}>#{element.strftime("%Y-%m-%d")}</#{element_name}>"
10
+ elsif element.class == Time or element.class == DateTime
11
+ str = "<#{element_name}>#{element.strftime("%Y-%m-%dT%H:%M:%SZ")}</#{element_name}>"
12
+ elsif element.kind_of? Struct or element.kind_of? Hash or element.kind_of? Array
13
+ str = element.to_xml
14
+ else
15
+ str = "<#{element_name}>#{element}</#{element_name}>"
16
+ end
17
+ end
18
+ def to_xml
19
+ str = "<array>"
20
+ self.each do |item|
21
+ str += render_xml("element",item)
22
+ end
23
+ str += "</array>"
24
+ end
25
+ def to(format)
26
+ case format
27
+ when 'xml'
28
+ self.to_xml
29
+ when 'json'
30
+ self.to_json
31
+ when 'string'
32
+ self.to_s
33
+ else
34
+ raise "invalid format: #{format}, use one of xml, json, or string"
35
+ end
36
+ end
37
+ end
38
+
39
+ class Hash
40
+ def render_xml(element_name, element)
41
+ str = ""
42
+ if element.class == Date
43
+ str = "<#{element_name}>#{element.strftime("%Y-%m-%d")}</#{element_name}>"
44
+ elsif element.class == Time or element.class == DateTime
45
+ str = "<#{element_name}>#{element.strftime("%Y-%m-%dT%H:%M:%SZ")}</#{element_name}>"
46
+ elsif element.kind_of? Struct or element.kind_of? Hash or element.kind_of? Array
47
+ str = element.to_xml
48
+ else
49
+ str = "<#{element_name}>#{element}</#{element_name}>"
50
+ end
51
+ end
52
+ def to_xml
53
+ str = "<hash>"
54
+ self.each do |key,value|
55
+ str += "<element><key>#{key}</key>"
56
+ str += render_xml("value",value)
57
+ str += "</element>"
58
+ end
59
+ str += "</hash>"
60
+ end
61
+ def to(format)
62
+ case format
63
+ when 'xml'
64
+ self.to_xml
65
+ when 'json'
66
+ self.to_json
67
+ when 'string'
68
+ self.to_s
69
+ else
70
+ raise "invalid format: #{format}, use one of xml, json, or string"
71
+ end
72
+ end
73
+ end
74
+
75
+ class Struct
76
+ @@printclass = true
77
+ def Struct::printclass=(pc)
78
+ @@printclass = pc
79
+ end
80
+ def render_xml(element_name, element)
81
+ str = ""
82
+ if element.class == Date
83
+ str = "<#{element_name}>#{element.strftime("%Y-%m-%d")}</#{element_name}>"
84
+ elsif element.class == Time or element.class == DateTime
85
+ str = "<#{element_name}>#{element.strftime("%Y-%m-%dT%H:%M:%SZ")}</#{element_name}>"
86
+ elsif element.kind_of? Struct
87
+ str = element.to_xml
88
+ elsif element.kind_of? Hash or element.kind_of? Array
89
+ str = element.to_xml(element_name)
90
+ else
91
+ str = "<#{element_name}>#{element}</#{element_name}>"
92
+ end
93
+ end
94
+ def to_xml
95
+ children = []
96
+ str = "<#{self.class}"
97
+ self.members.each do |member|
98
+ if self[member].class == Array or self[member].class == Hash or self[member].kind_of? Struct
99
+ children << member
100
+ elsif self[member].class == Date
101
+ str += " #{member}='#{self[member].strftime("%Y-%m-%d")}'"
102
+ elsif self[member].class == Time
103
+ str += " #{member}='#{self[member].strftime("%Y-%m-%dT%H:%M:%SZ")}'"
104
+ else
105
+ str += " #{member}='#{self[member]}'"
106
+ end
107
+ end
108
+ if children.length == 0
109
+ str += ' />'
110
+ else
111
+ str += '>'
112
+ children.each do |member|
113
+ if self[member].class == Array
114
+ str += "<#{member}s>"
115
+ self[member].each do |item|
116
+ str += render_xml(member,item)
117
+ end
118
+ str += "</#{member}s>"
119
+ elsif self[member].class == Hash
120
+ str += "<#{member}s>"
121
+ self[member].each do |key,value|
122
+ str += "<HashElement><key>#{key}</key><value>"
123
+ str += render_xml(member,value)
124
+ str += "</value></HashElement>"
125
+ end
126
+ str += "</#{member}s>"
127
+ elsif self[member].kind_of? Struct
128
+ str += self[member].to_xml
129
+ end
130
+ end
131
+ str += "</#{self.class}>"
132
+ end
133
+ end
134
+
135
+ def to_json(*a)
136
+ hash = (@@printclass) ? { 'class' => self.class } : {}
137
+ self.members.sort.each do |member|
138
+ hash[member] = self[member]
139
+ end
140
+ hash.to_json(*a)
141
+ end
142
+
143
+ def to(format)
144
+ case format
145
+ when 'xml'
146
+ self.to_xml
147
+ when 'json'
148
+ self.to_json
149
+ when 'string'
150
+ self.to_s
151
+ else
152
+ raise "invalid format: #{format}, use one of xml, json, or string"
153
+ end
154
+ end
155
+
156
+ def to_s(sep = " ")
157
+ self.members.map{ |x| self[x].to_s }.join(sep)
158
+ end
159
+
160
+ def to_s_header(sep = " ")
161
+ self.members.map{ |x| x.to_s }.join(sep)
162
+ end
163
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'passive-dns'
16
+
17
+ class Test::Unit::TestCase
18
+ end
@@ -0,0 +1,70 @@
1
+ require 'helper'
2
+
3
+ class TestPassiveDnsQuery < Test::Unit::TestCase
4
+ should "instantiate BFK Client" do
5
+ assert_not_nil(PassiveDNS::BFKClient.new)
6
+ end
7
+
8
+ should "instantiate DNSParse Client" do
9
+ assert_not_nil(PassiveDNS::DNSParse.new)
10
+ end
11
+
12
+ should "instantiate ISC Client" do
13
+ assert_not_nil(PassiveDNS::ISC.new)
14
+ end
15
+
16
+ should "instantiate CERT-EE Client" do
17
+ assert_not_nil(PassiveDNS::CERTEE.new)
18
+ end
19
+
20
+ should "instantiate Passive DNS State" do
21
+ assert_not_nil(PassiveDNS::PDNSToolState.new)
22
+ end
23
+
24
+ should "instantiate Passive DNS State database" do
25
+ if File.exists?("test/test.sqlite3")
26
+ File.unlink("test/test.sqlite3")
27
+ end
28
+ assert_not_nil(PassiveDNS::PDNSToolStateDB.new("test/test.sqlite3"))
29
+ if File.exists?("test/test.sqlite3")
30
+ File.unlink("test/test.sqlite3")
31
+ end
32
+ end
33
+
34
+ should "query BFK" do
35
+ rows = PassiveDNS::BFKClient.new.lookup("example.org");
36
+ assert_not_nil(rows)
37
+ assert_not_nil(rows.to_s)
38
+ assert_not_nil(rows.to_xml)
39
+ assert_not_nil(rows.to_json)
40
+ assert_not_nil(rows.to_yaml)
41
+ end
42
+
43
+ should "query DNSParse" do
44
+ rows = PassiveDNS::DNSParse.new.lookup("example.org");
45
+ assert_not_nil(rows)
46
+ assert_not_nil(rows.to_s)
47
+ assert_not_nil(rows.to_xml)
48
+ assert_not_nil(rows.to_json)
49
+ assert_not_nil(rows.to_yaml)
50
+ end
51
+
52
+ should "query ISC" do
53
+ rows = PassiveDNS::ISC.new.lookup("example.org");
54
+ assert_not_nil(rows)
55
+ assert_not_nil(rows.to_s)
56
+ assert_not_nil(rows.to_xml)
57
+ assert_not_nil(rows.to_json)
58
+ assert_not_nil(rows.to_yaml)
59
+ end
60
+
61
+ should "query CERTEE" do
62
+ rows = PassiveDNS::CERTEE.new.lookup("example.org");
63
+ assert_not_nil(rows)
64
+ assert_not_nil(rows.to_s)
65
+ assert_not_nil(rows.to_xml)
66
+ assert_not_nil(rows.to_json)
67
+ assert_not_nil(rows.to_yaml)
68
+ end
69
+
70
+ end
metadata ADDED
@@ -0,0 +1,225 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: passive-dns
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Chris Lee
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain:
17
+ - |
18
+ -----BEGIN CERTIFICATE-----
19
+ MIIDYjCCAkqgAwIBAgIBADANBgkqhkiG9w0BAQUFADBXMREwDwYDVQQDDAhydWJ5
20
+ Z2VtczEYMBYGCgmSJomT8ixkARkWCGNocmlzbGVlMRMwEQYKCZImiZPyLGQBGRYD
21
+ ZGhzMRMwEQYKCZImiZPyLGQBGRYDb3JnMB4XDTExMDIyNzE1MzAxOVoXDTEyMDIy
22
+ NzE1MzAxOVowVzERMA8GA1UEAwwIcnVieWdlbXMxGDAWBgoJkiaJk/IsZAEZFghj
23
+ aHJpc2xlZTETMBEGCgmSJomT8ixkARkWA2RoczETMBEGCgmSJomT8ixkARkWA29y
24
+ ZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALNM1Hjs6q58sf7Jp64A
25
+ vEY2cnRWDdFpD8UWpwaJK5kgSHOVgs+0mtszn+YlYjmx8kpmuYpyU4g9mNMImMQe
26
+ ow8pVsL4QBBK/1Ozgdxrsptk3IiTozMYA+g2I/+WvZSEDu9uHkKe8pvMBEMrg7RJ
27
+ IN7+jWaPnSzg3DbFwxwOdi+QRw33DjK7oFWcOaaBqWTUpI4epdi/c/FE1I6UWULJ
28
+ ZF/Uso0Sc2Pp/YuVhuMHGrUbn7zrWWo76nnK4DTLfXFDbZF5lIXT1w6BtIiN6Ho9
29
+ Rdr/W6663hYUo3WMsUSa3I5+PJXEBKmGHIZ2TNFnoFIRHha2fmm1HC9+BTaKwcO9
30
+ PLcCAwEAAaM5MDcwCQYDVR0TBAIwADAdBgNVHQ4EFgQURzsNkZo2rv86Ftc+hVww
31
+ RNICMrwwCwYDVR0PBAQDAgSwMA0GCSqGSIb3DQEBBQUAA4IBAQBRRw/iNA/PdnvW
32
+ OBoNCSr/IiHOGZqMHgPJwyWs68FhThnLc2EyIkuLTQf98ms1/D3p0XX9JsxazvKT
33
+ W/in8Mm/R2fkVziSdzqChtw/4Z4bW3c+RF7TgX6SP5cKxNAfKmAPuItcs2Y+7bdS
34
+ hr/FktVtT2iAmISRnlEbdaTpfl6N2ZWNT83khV6iOs5xRkX/+0e+GgAv9mE6nqr1
35
+ AkuDXMhposxcnFZUrZ3UtMPEe/JnyP7Vv6pvr3qtZm8FidFZU91+rX/fwdyBU8RP
36
+ /5l8uLWXXNt1wEbtu4N1I66LwTK2iRrQZE8XtlgZGbxYDFUkiurq3OafF2YwRs6W
37
+ 6yhklP75
38
+ -----END CERTIFICATE-----
39
+
40
+ date: 2011-03-06 00:00:00 -05:00
41
+ default_executable: pdnstool
42
+ dependencies:
43
+ - !ruby/object:Gem::Dependency
44
+ requirement: &id001 !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ hash: 1
50
+ segments:
51
+ - 1
52
+ - 4
53
+ - 3
54
+ version: 1.4.3
55
+ version_requirements: *id001
56
+ name: json
57
+ prerelease: false
58
+ type: :runtime
59
+ - !ruby/object:Gem::Dependency
60
+ requirement: &id002 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ hash: 29
66
+ segments:
67
+ - 1
68
+ - 3
69
+ - 3
70
+ version: 1.3.3
71
+ version_requirements: *id002
72
+ name: sqlite3
73
+ prerelease: false
74
+ type: :runtime
75
+ - !ruby/object:Gem::Dependency
76
+ requirement: &id003 !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ hash: 3
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ version_requirements: *id003
86
+ name: shoulda
87
+ prerelease: false
88
+ type: :development
89
+ - !ruby/object:Gem::Dependency
90
+ requirement: &id004 !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ~>
94
+ - !ruby/object:Gem::Version
95
+ hash: 23
96
+ segments:
97
+ - 1
98
+ - 0
99
+ - 0
100
+ version: 1.0.0
101
+ version_requirements: *id004
102
+ name: bundler
103
+ prerelease: false
104
+ type: :development
105
+ - !ruby/object:Gem::Dependency
106
+ requirement: &id005 !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ~>
110
+ - !ruby/object:Gem::Version
111
+ hash: 7
112
+ segments:
113
+ - 1
114
+ - 5
115
+ - 2
116
+ version: 1.5.2
117
+ version_requirements: *id005
118
+ name: jeweler
119
+ prerelease: false
120
+ type: :development
121
+ - !ruby/object:Gem::Dependency
122
+ requirement: &id006 !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ hash: 3
128
+ segments:
129
+ - 0
130
+ version: "0"
131
+ version_requirements: *id006
132
+ name: rcov
133
+ prerelease: false
134
+ type: :development
135
+ - !ruby/object:Gem::Dependency
136
+ requirement: &id007 !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ">"
140
+ - !ruby/object:Gem::Version
141
+ hash: 1
142
+ segments:
143
+ - 1
144
+ - 4
145
+ - 3
146
+ version: 1.4.3
147
+ version_requirements: *id007
148
+ name: json
149
+ prerelease: false
150
+ type: :runtime
151
+ - !ruby/object:Gem::Dependency
152
+ requirement: &id008 !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ hash: 29
158
+ segments:
159
+ - 1
160
+ - 3
161
+ - 3
162
+ version: 1.3.3
163
+ version_requirements: *id008
164
+ name: sqlite3
165
+ prerelease: false
166
+ type: :runtime
167
+ description: This provides interfaces to various passive DNS databases to do the query and to normalize the responses. The query tool also allows for recursive queries, using an SQLite3 database to keep state.
168
+ email: rubygems@chrislee.dhs.org
169
+ executables:
170
+ - pdnstool
171
+ extensions: []
172
+
173
+ extra_rdoc_files:
174
+ - LICENSE.txt
175
+ - README.rdoc
176
+ files:
177
+ - bin/pdnstool
178
+ - lib/passive-dns.rb
179
+ - lib/pdns/bfkclient.rb
180
+ - lib/pdns/certeepdns.rb
181
+ - lib/pdns/dnsparse.rb
182
+ - lib/pdns/iscpdns.rb
183
+ - lib/pdns/pdns.rb
184
+ - lib/util/structformatter.rb
185
+ - LICENSE.txt
186
+ - README.rdoc
187
+ - test/helper.rb
188
+ - test/test_passive-dns.rb
189
+ has_rdoc: true
190
+ homepage: http://github.com/chrislee35/passive-dns
191
+ licenses:
192
+ - MIT
193
+ post_install_message:
194
+ rdoc_options: []
195
+
196
+ require_paths:
197
+ - lib
198
+ required_ruby_version: !ruby/object:Gem::Requirement
199
+ none: false
200
+ requirements:
201
+ - - ">="
202
+ - !ruby/object:Gem::Version
203
+ hash: 3
204
+ segments:
205
+ - 0
206
+ version: "0"
207
+ required_rubygems_version: !ruby/object:Gem::Requirement
208
+ none: false
209
+ requirements:
210
+ - - ">="
211
+ - !ruby/object:Gem::Version
212
+ hash: 3
213
+ segments:
214
+ - 0
215
+ version: "0"
216
+ requirements: []
217
+
218
+ rubyforge_project:
219
+ rubygems_version: 1.5.0
220
+ signing_key:
221
+ specification_version: 3
222
+ summary: Query passive DNS databases
223
+ test_files:
224
+ - test/helper.rb
225
+ - test/test_passive-dns.rb
metadata.gz.sig ADDED
@@ -0,0 +1,2 @@
1
+ ��Y��pH�o׏�
2
+ v�kb��І��UH��;��~�KPK������աu�{T�U��z����$��t�`�V�NjO��e