passivedns-client 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data.tar.gz.sig +0 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +64 -0
- data/Rakefile +12 -0
- data/bin/pdnstool +202 -0
- data/lib/passivedns/client.rb +68 -0
- data/lib/passivedns/client/bfk.rb +58 -0
- data/lib/passivedns/client/certee.rb +36 -0
- data/lib/passivedns/client/dnsparse.rb +89 -0
- data/lib/passivedns/client/isc.rb +80 -0
- data/lib/passivedns/client/state.rb +272 -0
- data/lib/passivedns/client/version.rb +5 -0
- data/lib/passivedns/client/virustotal.rb +63 -0
- data/passivedns-client.gemspec +29 -0
- data/test/helper.rb +2 -0
- data/test/test_passivedns-client.rb +118 -0
- metadata +177 -0
- metadata.gz.sig +0 -0
@@ -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,89 @@
|
|
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").to_i
|
59
|
+
month = Time.now.strftime("%m").to_i
|
60
|
+
url = "#{@base}#{label}&year=#{year - 1}"
|
61
|
+
url = "#{@base}#{label}&year=#{year}" if month > 3
|
62
|
+
if label =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/
|
63
|
+
url.gsub!(/query\.php/,'cidr.php')
|
64
|
+
elsif label =~ /\*$/
|
65
|
+
url.gsub!(/query\.php/,'wildcard.php')
|
66
|
+
end
|
67
|
+
$stderr.puts "DEBUG: DNSParse url = #{url}" if @debug
|
68
|
+
url = URI.parse url
|
69
|
+
http = Net::HTTP.new(url.host, url.port)
|
70
|
+
http.use_ssl = (url.scheme == 'https')
|
71
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
72
|
+
http.verify_depth = 5
|
73
|
+
request = Net::HTTP::Get.new(url.path+"?"+url.query)
|
74
|
+
request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
|
75
|
+
request.basic_auth @user, @pass
|
76
|
+
t1 = Time.now
|
77
|
+
response = http.request(request)
|
78
|
+
t2 = Time.now
|
79
|
+
if @base =~ /format=json/
|
80
|
+
parse_json(response.body,t2-t1)
|
81
|
+
else
|
82
|
+
parse_html(response.body,t2-t1)
|
83
|
+
end
|
84
|
+
}
|
85
|
+
rescue Timeout::Error => e
|
86
|
+
$stderr.puts "DNSParse lookup timed out: #{label}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,80 @@
|
|
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"),record['count'])
|
39
|
+
else
|
40
|
+
res << PDNSResult.new('ISC',response_time,record['rrname'],rdata,record['rrtype'])
|
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} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
|
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
|
+
$stderr.puts response if @debug
|
74
|
+
parse_json(response.body,t2-t1)
|
75
|
+
}
|
76
|
+
rescue Timeout::Error => e
|
77
|
+
$stderr.puts "ISC lookup timed out: #{label}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
require 'yaml'
|
3
|
+
require 'structformatter'
|
4
|
+
|
5
|
+
module PassiveDNS
|
6
|
+
class PDNSQueueEntry < Struct.new(:query, :state, :level); end
|
7
|
+
|
8
|
+
class PDNSToolState
|
9
|
+
attr_accessor :debug
|
10
|
+
attr_reader :level
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@queue = []
|
14
|
+
@recs = []
|
15
|
+
@level = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def next_result
|
19
|
+
@recs.each do |rec|
|
20
|
+
yield rec
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_result(res)
|
25
|
+
@recs << res
|
26
|
+
add_query(res.answer,'pending')
|
27
|
+
add_query(res.query,'pending')
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_query(query,state)
|
31
|
+
@queue.each do |q|
|
32
|
+
if q.query == query
|
33
|
+
puts "update_query: #{query} (#{q.state}) -> (#{state})" if @debug
|
34
|
+
q.state = state
|
35
|
+
break
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_state(query)
|
41
|
+
@queue.each do |q|
|
42
|
+
if q.query == query
|
43
|
+
return q.state
|
44
|
+
end
|
45
|
+
end
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_query(query,state,level=@level+1)
|
50
|
+
if query =~ /^\d+ \w+\./
|
51
|
+
query = query.split(/ /,2)[1]
|
52
|
+
end
|
53
|
+
return if get_state(query)
|
54
|
+
puts "Adding query: #{query}, #{state}, #{level}" if @debug
|
55
|
+
@queue << PDNSQueueEntry.new(query,state,level)
|
56
|
+
end
|
57
|
+
|
58
|
+
def each_query(max_level=20)
|
59
|
+
@queue.each do |q|
|
60
|
+
if q.state == 'pending' or q.state == 'failed'
|
61
|
+
@level = q.level
|
62
|
+
q.state = 'queried'
|
63
|
+
if q.level < max_level
|
64
|
+
yield q.query
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_gdf
|
71
|
+
output = "nodedef> name,description VARCHAR(12),color,style\n"
|
72
|
+
# IP "$node2,,white,1"
|
73
|
+
# domain "$node2,,gray,2"
|
74
|
+
# Struct.new(:query, :answer, :rrtype, :ttl, :firstseen, :lastseen)
|
75
|
+
colors = {"MX" => "green", "A" => "blue", "CNAME" => "pink", "NS" => "red", "SOA" => "white", "PTR" => "purple", "TXT" => "brown"}
|
76
|
+
nodes = {}
|
77
|
+
edges = {}
|
78
|
+
next_result do |i|
|
79
|
+
if i
|
80
|
+
nodes[i.query + ",,gray,2"] = true
|
81
|
+
if i.answer =~ /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ then
|
82
|
+
nodes[i.answer + ",,white,1"] = true
|
83
|
+
else
|
84
|
+
nodes[i.answer + ",,gray,2"] = true
|
85
|
+
end
|
86
|
+
color = colors[i.rrtype]
|
87
|
+
color ||= "blue"
|
88
|
+
edges[i.query + "," + i.answer + "," + color] = true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
nodes.each do |i,j|
|
92
|
+
output += i+"\n"
|
93
|
+
end
|
94
|
+
output += "edgedef> node1,node2,color\n"
|
95
|
+
edges.each do |i,j|
|
96
|
+
output += i+"\n"
|
97
|
+
end
|
98
|
+
output
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_graphviz
|
102
|
+
colors = {"MX" => "green", "A" => "blue", "CNAME" => "pink", "NS" => "red", "SOA" => "white", "PTR" => "purple", "TXT" => "brown"}
|
103
|
+
output = "graph pdns {\n"
|
104
|
+
nodes = {}
|
105
|
+
next_result do |l|
|
106
|
+
if l
|
107
|
+
unless nodes[l.query]
|
108
|
+
output += " \"#{l.query}\" [shape=ellipse, style=filled, color=gray];\n"
|
109
|
+
if l.answer =~ /^\d{3}\.\d{3}\.\d{3}\.\d{3}$/
|
110
|
+
output += " \"#{l.answer}\" [shape=box, style=filled, color=white];\n"
|
111
|
+
else
|
112
|
+
output += " \"#{l.answer}\" [shape=ellipse, style=filled, color=gray];\n"
|
113
|
+
end
|
114
|
+
nodes[l.query] = true
|
115
|
+
end
|
116
|
+
output += " \"#{l.query}\" -- \"#{l.answer}\" [color=#{colors[l.rrtype]}];\n"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
output += "}\n"
|
120
|
+
end
|
121
|
+
|
122
|
+
def to_graphml
|
123
|
+
output = '<?xml version="1.0" encoding="UTF-8"?>
|
124
|
+
<graphml xmlns="http://graphml.graphdrawing.org/xmlns"
|
125
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
126
|
+
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
|
127
|
+
http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
|
128
|
+
<graph id="G" edgedefault="directed">
|
129
|
+
'
|
130
|
+
nodes = {}
|
131
|
+
edges = {}
|
132
|
+
next_result do |r|
|
133
|
+
if r
|
134
|
+
output += " <node id='#{r.query}'/>\n" unless nodes["#{r.query}"]
|
135
|
+
nodes[r.query] = true
|
136
|
+
output += " <node id='#{r.answer}'/>\n" unless nodes["#{r.answer}"]
|
137
|
+
nodes[r.answer] = true
|
138
|
+
output += " <edge source='#{r.query}' target='#{r.answer}'/>\n" unless edges["#{r.query}|#{r.answer}"]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
output += '</graph></graphml>'+"\n"
|
142
|
+
end
|
143
|
+
|
144
|
+
def to_xml
|
145
|
+
output = '<?xml version="1.0" encoding="UTF-8" ?>'+"\n"
|
146
|
+
output += "<report>\n"
|
147
|
+
output += " <results>\n"
|
148
|
+
next_result do |rec|
|
149
|
+
output += " "+rec.to_xml+"\n"
|
150
|
+
end
|
151
|
+
output += " </results>\n"
|
152
|
+
output += "</report>\n"
|
153
|
+
end
|
154
|
+
|
155
|
+
def to_yaml
|
156
|
+
output = ""
|
157
|
+
next_result do |rec|
|
158
|
+
output += rec.to_yaml+"\n"
|
159
|
+
end
|
160
|
+
output
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_json
|
164
|
+
output = "[\n"
|
165
|
+
sep = ""
|
166
|
+
next_result do |rec|
|
167
|
+
output += sep
|
168
|
+
output += rec.to_json
|
169
|
+
sep = ",\n"
|
170
|
+
end
|
171
|
+
output += "\n]\n"
|
172
|
+
end
|
173
|
+
|
174
|
+
def to_s(sep="\t")
|
175
|
+
output = ""
|
176
|
+
next_result do |rec|
|
177
|
+
output += rec.to_s(sep)+"\n"
|
178
|
+
end
|
179
|
+
output
|
180
|
+
end
|
181
|
+
end # class PDNSToolState
|
182
|
+
|
183
|
+
|
184
|
+
class PDNSToolStateDB < PDNSToolState
|
185
|
+
attr_reader :level
|
186
|
+
def initialize(sqlitedb=nil)
|
187
|
+
puts "PDNSToolState initialize #{sqlitedb}" if @debug
|
188
|
+
@level = 0
|
189
|
+
@sqlitedb = sqlitedb
|
190
|
+
raise "Cannot use this class without a database file" unless @sqlitedb
|
191
|
+
unless File.exists?(@sqlitedb)
|
192
|
+
newdb = true
|
193
|
+
end
|
194
|
+
@sqlitedbh = SQLite3::Database.new(@sqlitedb)
|
195
|
+
if newdb
|
196
|
+
create_tables
|
197
|
+
end
|
198
|
+
res = @sqlitedbh.execute("select min(level) from queue where state = 'pending'")
|
199
|
+
if res
|
200
|
+
res.each do |row|
|
201
|
+
@level = row[0].to_i
|
202
|
+
puts "changed @level = #{@level}" if @debug
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def create_tables
|
208
|
+
puts "creating tables" if @debug
|
209
|
+
@sqlitedbh.execute("create table results (query, answer, rrtype, ttl, firstseen, lastseen, ts REAL)")
|
210
|
+
@sqlitedbh.execute("create table queue (query, state, level INTEGER, ts REAL)")
|
211
|
+
@sqlitedbh.execute("create index residx on results (ts)")
|
212
|
+
@sqlitedbh.execute("create unique index queue_unique on queue (query)")
|
213
|
+
@sqlitedbh.execute("create index queue_level_idx on queue (level)")
|
214
|
+
@sqlitedbh.execute("create index queue_state_idx on queue (state)")
|
215
|
+
end
|
216
|
+
|
217
|
+
def next_result
|
218
|
+
rows = @sqlitedbh.execute("select query, answer, rrtype, ttl, firstseen, lastseen from results order by ts")
|
219
|
+
rows.each do |row|
|
220
|
+
yield PDNSResult.new(*row)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def add_result(res)
|
225
|
+
puts "adding result: #{res.to_s}" if @debug
|
226
|
+
curtime = Time.now().to_f
|
227
|
+
@sqlitedbh.execute("insert into results values ('#{res.query}','#{res.answer}','#{res.rrtype}','#{res.ttl}','#{res.firstseen}','#{res.lastseen}',#{curtime})")
|
228
|
+
|
229
|
+
add_query(res.answer,'pending')
|
230
|
+
add_query(res.query,'pending')
|
231
|
+
end
|
232
|
+
|
233
|
+
def add_query(query,state,level=@level+1)
|
234
|
+
return if get_state(query)
|
235
|
+
curtime = Time.now().to_f
|
236
|
+
begin
|
237
|
+
puts "add_query(#{query},#{state},level=#{level})" if @debug
|
238
|
+
@sqlitedbh.execute("insert into queue values ('#{query}','#{state}',#{level},#{curtime})")
|
239
|
+
rescue
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def update_query(query,state)
|
244
|
+
@sqlitedbh.execute("update queue set state = '#{state}' where query = '#{query}'")
|
245
|
+
end
|
246
|
+
|
247
|
+
def get_state(query)
|
248
|
+
rows = @sqlitedbh.execute("select state from queue where query = '#{query}'")
|
249
|
+
if rows
|
250
|
+
rows.each do |row|
|
251
|
+
return row[0]
|
252
|
+
end
|
253
|
+
end
|
254
|
+
false
|
255
|
+
end
|
256
|
+
|
257
|
+
def each_query(max_level=20)
|
258
|
+
puts "each_query max_level=#{max_level} curlevel=#{@level}" if @debug
|
259
|
+
rows = @sqlitedbh.execute("select query, state, level from queue where state = 'failed' or state = 'pending' order by level limit 1")
|
260
|
+
if rows
|
261
|
+
rows.each do |row|
|
262
|
+
query,state,level = row
|
263
|
+
puts " #{query},#{state},#{level}" if @debug
|
264
|
+
if level < max_level
|
265
|
+
update_query(query,'queried')
|
266
|
+
yield query
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end # class PDNSToolStateDB
|
272
|
+
end
|