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 +0 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/bin/pdnstool +192 -0
- data/lib/passive-dns.rb +5 -0
- data/lib/pdns/bfkclient.rb +58 -0
- data/lib/pdns/certeepdns.rb +36 -0
- data/lib/pdns/dnsparse.rb +88 -0
- data/lib/pdns/iscpdns.rb +79 -0
- data/lib/pdns/pdns.rb +280 -0
- data/lib/util/structformatter.rb +163 -0
- data/test/helper.rb +18 -0
- data/test/test_passive-dns.rb +70 -0
- metadata +225 -0
- metadata.gz.sig +2 -0
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
|
data/lib/passive-dns.rb
ADDED
@@ -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
|
data/lib/pdns/iscpdns.rb
ADDED
@@ -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