passivedns-client 2.1.6 → 2.1.7
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.
- checksums.yaml +4 -4
- data/lib/passivedns/client.rb +68 -36
- data/lib/passivedns/client/cli.rb +139 -139
- data/lib/passivedns/client/provider/bfk.rb +54 -52
- data/lib/passivedns/client/provider/circl.rb +39 -39
- data/lib/passivedns/client/provider/cn360.rb +21 -21
- data/lib/passivedns/client/provider/dnsdb.rb +56 -56
- data/lib/passivedns/client/provider/mnemonic.rb +62 -55
- data/lib/passivedns/client/provider/passivetotal.rb +43 -43
- data/lib/passivedns/client/provider/riskiq.rb +40 -40
- data/lib/passivedns/client/provider/tcpiputils.rb +18 -18
- data/lib/passivedns/client/provider/virustotal.rb +46 -46
- data/lib/passivedns/client/state.rb +236 -236
- data/lib/passivedns/client/version.rb +1 -1
- metadata +2 -2
@@ -7,7 +7,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
7
7
|
# The Provider module contains all the Passive DNS provider client code
|
8
8
|
module Provider
|
9
9
|
# Queries VirusTotal's passive DNS database
|
10
|
-
|
10
|
+
class VirusTotal < PassiveDB
|
11
11
|
# Sets the modules self-reported name to "VirusTotal"
|
12
12
|
def self.name
|
13
13
|
"VirusTotal"
|
@@ -39,7 +39,7 @@ module PassiveDNS #:nodoc: don't document this
|
|
39
39
|
#
|
40
40
|
# PassiveDNS::Provider::VirusTotal.new(options)
|
41
41
|
#
|
42
|
-
|
42
|
+
def initialize(options={})
|
43
43
|
@debug = options[:debug] || false
|
44
44
|
@apikey = options["APIKEY"] || raise("#{self.class.name} requires an APIKEY. See README.md")
|
45
45
|
@url = options["URL"] || "https://www.virustotal.com/vtapi/v2/"
|
@@ -47,59 +47,59 @@ module PassiveDNS #:nodoc: don't document this
|
|
47
47
|
|
48
48
|
# Takes a label (either a domain or an IP address) and returns
|
49
49
|
# an array of PassiveDNS::PDNSResult instances with the answers to the query
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
50
|
+
def lookup(label, limit=nil)
|
51
|
+
$stderr.puts "DEBUG: #{self.class.name}.lookup(#{label})" if @debug
|
52
|
+
Timeout::timeout(240) {
|
53
|
+
url = nil
|
54
|
+
if label =~ /^[\d\.]+$/
|
55
|
+
url = "#{@url}ip-address/report?ip=#{label}&apikey=#{@apikey}"
|
56
|
+
else
|
57
|
+
url = "#{@url}domain/report?domain=#{label}&apikey=#{@apikey}"
|
58
|
+
end
|
59
|
+
$stderr.puts "DEBUG: #{self.class.name} url = #{url}" if @debug
|
60
|
+
url = URI.parse url
|
61
|
+
http = Net::HTTP.new(url.host, url.port)
|
62
|
+
http.use_ssl = (url.scheme == 'https')
|
63
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
64
|
+
http.verify_depth = 5
|
65
|
+
request = Net::HTTP::Get.new(url.path+"?"+url.query)
|
66
|
+
request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivedns-client rubygem v#{PassiveDNS::Client::VERSION}")
|
67
|
+
t1 = Time.now
|
68
|
+
response = http.request(request)
|
69
|
+
t2 = Time.now
|
70
|
+
recs = parse_json(response.body, label, t2-t1)
|
71
71
|
if limit
|
72
72
|
recs[0,limit]
|
73
73
|
else
|
74
74
|
recs
|
75
75
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
76
|
+
}
|
77
|
+
rescue Timeout::Error => e
|
78
|
+
$stderr.puts "#{self.class.name} lookup timed out: #{label}"
|
79
|
+
end
|
80
80
|
|
81
81
|
private
|
82
82
|
|
83
83
|
# parses the response of virustotal's JSON reply to generate an array of PDNSResult
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
84
|
+
def parse_json(page,query,response_time=0)
|
85
|
+
res = []
|
86
|
+
data = JSON.parse(page)
|
87
|
+
if data['resolutions']
|
88
|
+
data['resolutions'].each do |row|
|
89
89
|
lastseen = Time.parse(row['last_resolved']+" +0000")
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
90
|
+
if row['ip_address']
|
91
|
+
res << PDNSResult.new(self.class.name,response_time,query,row['ip_address'],'A',nil,nil,lastseen, 'yellow')
|
92
|
+
elsif row['hostname']
|
93
|
+
res << PDNSResult.new(self.class.name,response_time,row['hostname'],query,'A',nil,nil,lastseen, 'yellow')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
res
|
98
|
+
rescue Exception => e
|
99
|
+
$stderr.puts "VirusTotal Exception: #{e}"
|
100
|
+
raise e
|
101
|
+
end
|
102
102
|
|
103
|
-
|
103
|
+
end
|
104
104
|
end
|
105
|
-
end
|
105
|
+
end
|
@@ -4,297 +4,297 @@ require 'structformatter'
|
|
4
4
|
|
5
5
|
module PassiveDNS # :nodoc:
|
6
6
|
# struct to hold pending entries for query
|
7
|
-
|
7
|
+
class PDNSQueueEntry < Struct.new(:query, :state, :level); end
|
8
8
|
|
9
9
|
# holds state in memory of the queue to be queried, records returned, and the level of recursion
|
10
|
-
|
10
|
+
class PDNSToolState
|
11
11
|
# :debug enables verbose logging to standard output
|
12
|
-
|
12
|
+
attr_accessor :debug
|
13
13
|
# :level is the recursion depth
|
14
|
-
|
14
|
+
attr_reader :level
|
15
15
|
|
16
16
|
# creates a new, blank PDNSToolState instance
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
def initialize
|
18
|
+
@queue = []
|
19
|
+
@recs = []
|
20
|
+
@level = 0
|
21
|
+
end
|
22
22
|
|
23
23
|
# returns the next record
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
def next_result
|
25
|
+
@recs.each do |rec|
|
26
|
+
yield rec
|
27
|
+
end
|
28
|
+
end
|
29
29
|
|
30
30
|
# adds the record to the list of records received and tries to add the answer and query back to the queue for future query
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
def add_result(res)
|
32
|
+
@recs << res
|
33
|
+
add_query(res.answer,'pending')
|
34
|
+
add_query(res.query,'pending')
|
35
|
+
end
|
36
36
|
|
37
37
|
# sets the state of a given query
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
47
|
|
48
48
|
# returns the state of a provided query
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
49
|
+
def get_state(query)
|
50
|
+
@queue.each do |q|
|
51
|
+
if q.query == query
|
52
|
+
return q.state
|
53
|
+
end
|
54
|
+
end
|
55
|
+
false
|
56
|
+
end
|
57
57
|
|
58
58
|
# adding a query to the queue of things to be queried, but only if the query isn't already queued or answered
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
59
|
+
def add_query(query,state,level=@level+1)
|
60
|
+
if query =~ /^\d+ \w+\./
|
61
|
+
query = query.split(/ /,2)[1]
|
62
|
+
end
|
63
|
+
return if get_state(query)
|
64
|
+
puts "Adding query: #{query}, #{state}, #{level}" if @debug
|
65
|
+
@queue << PDNSQueueEntry.new(query,state,level)
|
66
|
+
end
|
67
67
|
|
68
68
|
# returns each query waiting on the queue
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
69
|
+
def each_query(max_level=20)
|
70
|
+
@queue.each do |q|
|
71
|
+
if q.state == 'pending' or q.state == 'failed'
|
72
|
+
@level = q.level
|
73
|
+
q.state = 'queried'
|
74
|
+
if q.level < max_level
|
75
|
+
yield q.query
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
80
|
|
81
81
|
# transforms a set of results into GDF syntax
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
82
|
+
def to_gdf
|
83
|
+
output = "nodedef> name,description VARCHAR(12),color,style\n"
|
84
|
+
# IP "$node2,,white,1"
|
85
|
+
# domain "$node2,,gray,2"
|
86
|
+
# Struct.new(:query, :answer, :rrtype, :ttl, :firstseen, :lastseen)
|
87
|
+
colors = {"MX" => "green", "A" => "blue", "CNAME" => "pink", "NS" => "red", "SOA" => "white", "PTR" => "purple", "TXT" => "brown"}
|
88
|
+
nodes = {}
|
89
|
+
edges = {}
|
90
|
+
next_result do |i|
|
91
|
+
if i
|
92
|
+
nodes[i.query + ",,gray,2"] = true
|
93
|
+
if i.answer =~ /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/ then
|
94
|
+
nodes[i.answer + ",,white,1"] = true
|
95
|
+
else
|
96
|
+
nodes[i.answer + ",,gray,2"] = true
|
97
|
+
end
|
98
|
+
color = colors[i.rrtype]
|
99
|
+
color ||= "blue"
|
100
|
+
edges[i.query + "," + i.answer + "," + color] = true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
nodes.each do |i,j|
|
104
|
+
output += i+"\n"
|
105
|
+
end
|
106
|
+
output += "edgedef> node1,node2,color\n"
|
107
|
+
edges.each do |i,j|
|
108
|
+
output += i+"\n"
|
109
|
+
end
|
110
|
+
output
|
111
|
+
end
|
112
112
|
|
113
113
|
# transforms a set of results into graphviz syntax
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
114
|
+
def to_graphviz
|
115
|
+
colors = {"MX" => "green", "A" => "blue", "CNAME" => "pink", "NS" => "red", "SOA" => "white", "PTR" => "purple", "TXT" => "brown"}
|
116
|
+
output = "graph pdns {\n"
|
117
|
+
nodes = {}
|
118
|
+
next_result do |l|
|
119
|
+
if l
|
120
|
+
unless nodes[l.query]
|
121
|
+
output += " \"#{l.query}\" [shape=ellipse, style=filled, color=gray];\n"
|
122
|
+
if l.answer =~ /^\d{3}\.\d{3}\.\d{3}\.\d{3}$/
|
123
|
+
output += " \"#{l.answer}\" [shape=box, style=filled, color=white];\n"
|
124
|
+
else
|
125
|
+
output += " \"#{l.answer}\" [shape=ellipse, style=filled, color=gray];\n"
|
126
|
+
end
|
127
|
+
nodes[l.query] = true
|
128
|
+
end
|
129
|
+
output += " \"#{l.query}\" -- \"#{l.answer}\" [color=#{colors[l.rrtype]}];\n"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
output += "}\n"
|
133
|
+
end
|
134
134
|
|
135
135
|
# transforms a set of results into graphml syntax
|
136
|
-
|
137
|
-
|
136
|
+
def to_graphml
|
137
|
+
output = '<?xml version="1.0" encoding="UTF-8"?>
|
138
138
|
<graphml xmlns="http://graphml.graphdrawing.org/xmlns"
|
139
139
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
140
140
|
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
|
141
141
|
http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
|
142
142
|
<graph id="G" edgedefault="directed">
|
143
143
|
'
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
144
|
+
nodes = {}
|
145
|
+
edges = {}
|
146
|
+
next_result do |r|
|
147
|
+
if r
|
148
|
+
output += " <node id='#{r.query}'/>\n" unless nodes["#{r.query}"]
|
149
|
+
nodes[r.query] = true
|
150
|
+
output += " <node id='#{r.answer}'/>\n" unless nodes["#{r.answer}"]
|
151
|
+
nodes[r.answer] = true
|
152
|
+
output += " <edge source='#{r.query}' target='#{r.answer}'/>\n" unless edges["#{r.query}|#{r.answer}"]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
output += '</graph></graphml>'+"\n"
|
156
|
+
end
|
157
157
|
|
158
158
|
# transforms a set of results into XML
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
159
|
+
def to_xml
|
160
|
+
output = '<?xml version="1.0" encoding="UTF-8" ?>'+"\n"
|
161
|
+
output += "<report>\n"
|
162
|
+
output += " <results>\n"
|
163
|
+
next_result do |rec|
|
164
|
+
output += " "+rec.to_xml+"\n"
|
165
|
+
end
|
166
|
+
output += " </results>\n"
|
167
|
+
output += "</report>\n"
|
168
|
+
end
|
169
169
|
|
170
170
|
# transforms a set of results into YAML
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
171
|
+
def to_yaml
|
172
|
+
output = ""
|
173
|
+
next_result do |rec|
|
174
|
+
output += rec.to_yaml+"\n"
|
175
|
+
end
|
176
|
+
output
|
177
|
+
end
|
178
178
|
|
179
179
|
# transforms a set of results into JSON
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
180
|
+
def to_json
|
181
|
+
output = "[\n"
|
182
|
+
sep = ""
|
183
|
+
next_result do |rec|
|
184
|
+
output += sep
|
185
|
+
output += rec.to_json
|
186
|
+
sep = ",\n"
|
187
|
+
end
|
188
|
+
output += "\n]\n"
|
189
|
+
end
|
190
190
|
|
191
191
|
# transforms a set of results into a text string
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
192
|
+
def to_s(sep="\t")
|
193
|
+
output = ""
|
194
|
+
next_result do |rec|
|
195
|
+
output += rec.to_s(sep)+"\n"
|
196
|
+
end
|
197
|
+
output
|
198
|
+
end
|
199
|
+
end # class PDNSToolState
|
200
200
|
|
201
201
|
|
202
202
|
# creates persistence to the tool state by leveraging SQLite3
|
203
|
-
|
204
|
-
|
203
|
+
class PDNSToolStateDB < PDNSToolState
|
204
|
+
attr_reader :level
|
205
205
|
# creates an SQLite3-based Passive DNS Client state
|
206
206
|
# only argument is the filename of the sqlite3 database
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
207
|
+
def initialize(sqlitedb=nil)
|
208
|
+
puts "PDNSToolState initialize #{sqlitedb}" if @debug
|
209
|
+
@level = 0
|
210
|
+
@sqlitedb = sqlitedb
|
211
|
+
raise "Cannot use this class without a database file" unless @sqlitedb
|
212
|
+
unless File.exists?(@sqlitedb)
|
213
|
+
newdb = true
|
214
|
+
end
|
215
|
+
@sqlitedbh = SQLite3::Database.new(@sqlitedb)
|
216
|
+
if newdb
|
217
|
+
create_tables
|
218
|
+
end
|
219
|
+
res = @sqlitedbh.execute("select min(level) from queue where state = 'pending'")
|
220
|
+
if res
|
221
|
+
res.each do |row|
|
222
|
+
@level = row[0].to_i
|
223
|
+
puts "changed @level = #{@level}" if @debug
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
227
|
|
228
228
|
# creates the sqlite3 tables needed to track the state of this tool as itqueries and recurses
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
229
|
+
def create_tables
|
230
|
+
puts "creating tables" if @debug
|
231
|
+
@sqlitedbh.execute("create table results (query, answer, rrtype, ttl, firstseen, lastseen, ts REAL)")
|
232
|
+
@sqlitedbh.execute("create table queue (query, state, level INTEGER, ts REAL)")
|
233
|
+
@sqlitedbh.execute("create index residx on results (ts)")
|
234
|
+
@sqlitedbh.execute("create unique index queue_unique on queue (query)")
|
235
|
+
@sqlitedbh.execute("create index queue_level_idx on queue (level)")
|
236
|
+
@sqlitedbh.execute("create index queue_state_idx on queue (state)")
|
237
|
+
end
|
238
238
|
|
239
239
|
# returns the next record
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
240
|
+
def next_result
|
241
|
+
rows = @sqlitedbh.execute("select query, answer, rrtype, ttl, firstseen, lastseen from results order by ts")
|
242
|
+
rows.each do |row|
|
243
|
+
yield PDNSResult.new(*row)
|
244
|
+
end
|
245
|
+
end
|
246
246
|
|
247
247
|
# adds the record to the list of records received and tries to add the answer and query back to the queue for future query
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
248
|
+
def add_result(res)
|
249
|
+
puts "adding result: #{res.to_s}" if @debug
|
250
|
+
curtime = Time.now().to_f
|
251
|
+
@sqlitedbh.execute("insert into results values ('#{res.query}','#{res.answer}','#{res.rrtype}','#{res.ttl}','#{res.firstseen}','#{res.lastseen}',#{curtime})")
|
252
252
|
|
253
|
-
|
254
|
-
|
255
|
-
|
253
|
+
add_query(res.answer,'pending')
|
254
|
+
add_query(res.query,'pending')
|
255
|
+
end
|
256
256
|
|
257
257
|
# adding a query to the queue of things to be queried, but only if the query isn't already queued or answered
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
258
|
+
def add_query(query,state,level=@level+1)
|
259
|
+
return if get_state(query)
|
260
|
+
curtime = Time.now().to_f
|
261
|
+
begin
|
262
|
+
puts "add_query(#{query},#{state},level=#{level})" if @debug
|
263
|
+
@sqlitedbh.execute("insert into queue values ('#{query}','#{state}',#{level},#{curtime})")
|
264
|
+
rescue
|
265
|
+
end
|
266
|
+
end
|
267
267
|
|
268
268
|
# sets the state of a given query
|
269
|
-
|
270
|
-
|
271
|
-
|
269
|
+
def update_query(query,state)
|
270
|
+
@sqlitedbh.execute("update queue set state = '#{state}' where query = '#{query}'")
|
271
|
+
end
|
272
272
|
|
273
273
|
# returns each query waiting on the queue
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
274
|
+
def get_state(query)
|
275
|
+
rows = @sqlitedbh.execute("select state from queue where query = '#{query}'")
|
276
|
+
if rows
|
277
|
+
rows.each do |row|
|
278
|
+
return row[0]
|
279
|
+
end
|
280
|
+
end
|
281
|
+
false
|
282
|
+
end
|
283
283
|
|
284
284
|
# returns each query waiting on the queue
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
end
|
285
|
+
def each_query(max_level=20)
|
286
|
+
puts "each_query max_level=#{max_level} curlevel=#{@level}" if @debug
|
287
|
+
rows = @sqlitedbh.execute("select query, state, level from queue where state = 'failed' or state = 'pending' order by level limit 1")
|
288
|
+
if rows
|
289
|
+
rows.each do |row|
|
290
|
+
query,state,level = row
|
291
|
+
puts " #{query},#{state},#{level}" if @debug
|
292
|
+
if level < max_level
|
293
|
+
update_query(query,'queried')
|
294
|
+
yield query
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end # class PDNSToolStateDB
|
300
|
+
end
|