dnsruby 1.73.1 → 2.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +16 -4
- data/DNSSEC +3 -2
- data/EXAMPLES +1 -1
- data/README.md +370 -64
- data/RELEASE_NOTES.md +314 -1
- data/Rakefile +0 -3
- data/demo/axfr.rb +24 -6
- data/dnsruby.gemspec +6 -4
- data/lib/dnsruby/cache.rb +279 -49
- data/lib/dnsruby/code_mapper.rb +35 -8
- data/lib/dnsruby/code_mappers.rb +63 -49
- data/lib/dnsruby/config.rb +77 -4
- data/lib/dnsruby/deadline_queue.rb +75 -0
- data/lib/dnsruby/dnssec.rb +148 -45
- data/lib/dnsruby/ipv6.rb +45 -5
- data/lib/dnsruby/key_cache.rb +202 -35
- data/lib/dnsruby/message/decoder.rb +85 -19
- data/lib/dnsruby/message/encoder.rb +27 -16
- data/lib/dnsruby/message/header.rb +8 -12
- data/lib/dnsruby/message/message.rb +53 -22
- data/lib/dnsruby/message/section.rb +12 -34
- data/lib/dnsruby/name.rb +5 -2
- data/lib/dnsruby/packet_sender.rb +664 -207
- data/lib/dnsruby/presentation.rb +147 -0
- data/lib/dnsruby/query_control.rb +132 -0
- data/lib/dnsruby/recursor.rb +798 -341
- data/lib/dnsruby/resolver.rb +472 -176
- data/lib/dnsruby/resource/CAA.rb +67 -16
- data/lib/dnsruby/resource/DLV.rb +6 -0
- data/lib/dnsruby/resource/DNSKEY.rb +108 -16
- data/lib/dnsruby/resource/DS.rb +27 -28
- data/lib/dnsruby/resource/HTTPS.rb +16 -0
- data/lib/dnsruby/resource/IN.rb +21 -2
- data/lib/dnsruby/resource/MX.rb +2 -2
- data/lib/dnsruby/resource/NSEC.rb +6 -10
- data/lib/dnsruby/resource/NSEC3.rb +55 -19
- data/lib/dnsruby/resource/NSEC3PARAM.rb +4 -6
- data/lib/dnsruby/resource/OPT.rb +2 -3
- data/lib/dnsruby/resource/RR.rb +108 -65
- data/lib/dnsruby/resource/RRSIG.rb +5 -5
- data/lib/dnsruby/resource/RRSet.rb +38 -22
- data/lib/dnsruby/resource/SRV.rb +2 -2
- data/lib/dnsruby/resource/SVCB.rb +775 -0
- data/lib/dnsruby/resource/TSIG.rb +266 -231
- data/lib/dnsruby/resource/TXT.rb +34 -42
- data/lib/dnsruby/resource/domain_name.rb +1 -1
- data/lib/dnsruby/resource/generic.rb +31 -0
- data/lib/dnsruby/resource/generic_cache.rb +384 -0
- data/lib/dnsruby/resource/generic_constants.rb +79 -0
- data/lib/dnsruby/select_thread.rb +986 -291
- data/lib/dnsruby/serial_time.rb +25 -0
- data/lib/dnsruby/single_resolver.rb +25 -6
- data/lib/dnsruby/single_verifier.rb +1620 -530
- data/lib/dnsruby/transport_clock.rb +9 -0
- data/lib/dnsruby/update.rb +8 -6
- data/lib/dnsruby/validation_budget.rb +143 -0
- data/lib/dnsruby/validator_thread.rb +139 -58
- data/lib/dnsruby/version.rb +1 -1
- data/lib/dnsruby/zone_reader.rb +196 -129
- data/lib/dnsruby/zone_transfer.rb +157 -25
- data/lib/dnsruby.rb +7 -2
- data/test/ci_watchdog.rb +33 -0
- data/test/fixtures/org-ds-2026-11-05.zone +5 -0
- data/test/fixtures/root-dnskey-2026-11-05.zone +7 -0
- data/test/fork_test_helper.rb +37 -0
- data/test/local_dns_server.rb +57 -0
- data/test/soak_server.rb +152 -0
- data/test/socket_helpers.rb +26 -0
- data/test/spec_helper.rb +8 -13
- data/test/tc_attempt_configuration.rb +137 -0
- data/test/tc_caa.rb +2 -2
- data/test/tc_cache_keys.rb +36 -0
- data/test/tc_cancelled_send_capacity.rb +158 -0
- data/test/tc_dnssec_alias_validation.rb +289 -0
- data/test/tc_dnssec_budget.rb +489 -0
- data/test/tc_dnssec_cache_bounds.rb +197 -0
- data/test/tc_dnssec_cache_lifetime.rb +615 -0
- data/test/tc_dnssec_denial_proofs.rb +285 -0
- data/test/tc_dnssec_recursion_limits.rb +186 -0
- data/test/tc_dnssec_recursor_review_fixes.rb +351 -0
- data/test/tc_dnssec_trust_anchors.rb +286 -0
- data/test/tc_dnssec_trust_races.rb +147 -0
- data/test/tc_dnssec_wildcard_proofs.rb +249 -0
- data/test/tc_ds.rb +5 -5
- data/test/tc_edns_retry_regressions.rb +255 -0
- data/test/tc_escapedchars.rb +8 -0
- data/test/tc_final_review_fixes.rb +167 -0
- data/test/tc_final_transport_contracts.rb +104 -0
- data/test/tc_generic_cache.rb +852 -0
- data/test/tc_header.rb +26 -0
- data/test/tc_ipv6.rb +120 -0
- data/test/tc_key_cache_identity.rb +382 -0
- data/test/tc_malformed_tcp.rb +712 -0
- data/test/tc_name.rb +99 -5
- data/test/tc_nameserver_bootstrap.rb +322 -0
- data/test/tc_nsec.rb +4 -45
- data/test/tc_nsec3.rb +17 -1
- data/test/tc_pipeline_retirement.rb +358 -0
- data/test/tc_pipeline_retry.rb +462 -0
- data/test/tc_pipeline_send.rb +663 -0
- data/test/tc_protocol_review_fixes.rb +95 -0
- data/test/tc_query_control.rb +90 -0
- data/test/tc_record_codec_contracts.rb +107 -0
- data/test/tc_record_regressions.rb +225 -0
- data/test/tc_recur.rb +1 -0
- data/test/tc_recursor_address_fallback.rb +169 -0
- data/test/tc_recursor_limits.rb +139 -0
- data/test/tc_release_auxiliary_fixes.rb +288 -0
- data/test/tc_release_configuration_fixes.rb +612 -0
- data/test/tc_release_dnssec_completion.rb +405 -0
- data/test/tc_release_dnssec_fixes.rb +305 -0
- data/test/tc_release_negative_cache.rb +327 -0
- data/test/tc_release_recursor_cache.rb +212 -0
- data/test/tc_release_review_transport.rb +372 -0
- data/test/tc_release_review_transport_native.rb +56 -0
- data/test/tc_release_transport.rb +906 -0
- data/test/tc_release_transport_fixes.rb +196 -0
- data/test/tc_release_zone_defaults.rb +157 -0
- data/test/tc_release_zone_ttls.rb +39 -0
- data/test/tc_res_config.rb +53 -2
- data/test/tc_res_opt.rb +21 -1
- data/test/tc_resolver.rb +34 -19
- data/test/tc_resolver_concurrency.rb +383 -0
- data/test/tc_resolver_configuration_contracts.rb +31 -0
- data/test/tc_resolver_validation.rb +211 -0
- data/test/tc_review_cache.rb +163 -0
- data/test/tc_review_code_mapper.rb +279 -0
- data/test/tc_review_control_deadlines.rb +169 -0
- data/test/tc_review_followup_transport.rb +583 -0
- data/test/tc_review_packet_contracts.rb +369 -0
- data/test/tc_review_presentation.rb +213 -0
- data/test/tc_review_record_compatibility.rb +101 -0
- data/test/tc_review_transport_fixes.rb +195 -0
- data/test/tc_review_transport_native.rb +415 -0
- data/test/tc_review_validation_priming.rb +178 -0
- data/test/tc_rr-opt.rb +5 -2
- data/test/tc_rr-unknown.rb +19 -0
- data/test/tc_rr.rb +14 -0
- data/test/tc_rrset_grouping.rb +50 -0
- data/test/tc_rrset_review_fixes.rb +58 -0
- data/test/tc_rrsig.rb +21 -1
- data/test/tc_scoped_ipv6.rb +153 -0
- data/test/tc_scoped_ipv6_select.rb +157 -0
- data/test/tc_scoped_ipv6_transport.rb +329 -0
- data/test/tc_selector_wakeup.rb +42 -0
- data/test/tc_soak.rb +118 -196
- data/test/tc_socket_helpers.rb +90 -0
- data/test/tc_supporting_lookup_limits.rb +252 -0
- data/test/tc_svcb.rb +1043 -0
- data/test/tc_svcb_signed_target.rb +145 -0
- data/test/tc_svcb_transport.rb +120 -0
- data/test/tc_tcp.rb +69 -43
- data/test/tc_tcp_pipelining.rb +95 -204
- data/test/tc_transport_fork.rb +179 -0
- data/test/tc_tsig.rb +2 -2
- data/test/tc_tsig_api.rb +251 -0
- data/test/tc_tsig_rfc8945.rb +357 -0
- data/test/tc_v2_release_dnssec.rb +442 -0
- data/test/tc_v2_release_transport.rb +304 -0
- data/test/tc_v2_zone_transfer_native.rb +58 -0
- data/test/tc_zone_reader.rb +217 -1
- data/test/tcp_pipelining_server.rb +150 -0
- data/test/ts_offline.rb +77 -6
- data/test/ts_online.rb +2 -1
- metadata +127 -14
- data/.coveralls.yml +0 -2
data/lib/dnsruby/zone_reader.rb
CHANGED
|
@@ -25,12 +25,10 @@ module Dnsruby
|
|
|
25
25
|
# Create a new ZoneReader. The zone origin is required. If the desired SOA minimum
|
|
26
26
|
# and TTL are passed in, then they are used as default values.
|
|
27
27
|
def initialize(origin, soa_minimum = nil, soa_ttl = nil)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!Name.create(@origin).absolute?)
|
|
31
|
-
@origin = @origin.to_s + "."
|
|
32
|
-
end
|
|
28
|
+
origin_name = parse_zone_name(origin)
|
|
29
|
+
@origin = Name.new(origin_name.labels, true).to_s(true)
|
|
33
30
|
@soa_ttl = soa_ttl
|
|
31
|
+
@default_ttl = nil
|
|
34
32
|
if (soa_minimum && !@last_explicit_ttl)
|
|
35
33
|
@last_explicit_ttl = soa_minimum
|
|
36
34
|
else
|
|
@@ -39,6 +37,7 @@ module Dnsruby
|
|
|
39
37
|
@last_explicit_class = Classes.new("IN")
|
|
40
38
|
@last_name = nil
|
|
41
39
|
@continued_line = nil
|
|
40
|
+
@group_depth = 0
|
|
42
41
|
@in_quoted_section = false
|
|
43
42
|
end
|
|
44
43
|
|
|
@@ -72,60 +71,62 @@ module Dnsruby
|
|
|
72
71
|
raise ParseException.new("Error reading line #{io.lineno} of #{io.inspect} : [#{line}]")
|
|
73
72
|
end
|
|
74
73
|
end
|
|
74
|
+
if @continued_line
|
|
75
|
+
raise ParseException.new("Unclosed parenthesis at end of #{io.inspect}")
|
|
76
|
+
end
|
|
75
77
|
return zone
|
|
76
78
|
end
|
|
77
79
|
|
|
78
80
|
# Process the next line of the file
|
|
79
81
|
# Returns a string representing the normalised line.
|
|
80
82
|
def process_line(line, do_prefix_hack = false)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
if !@continued_line
|
|
84
|
+
@in_quoted_section = false
|
|
85
|
+
@single_quoted_start = if /['(]/.match?(line)
|
|
86
|
+
Presentation.single_quoted_start(line, owner_omitted: line.start_with?(' ', "\t"))
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
previous_quote = @in_quoted_section
|
|
90
|
+
line, @group_depth, @in_quoted_section =
|
|
91
|
+
Presentation.scan(line, @group_depth, @in_quoted_section,
|
|
92
|
+
single_quoted_from: @continued_line && @single_quoted_start ? 0 : @single_quoted_start)
|
|
93
|
+
|
|
94
|
+
if @continued_line
|
|
95
|
+
# The scanner keeps literal newlines inside quotes and turns layout
|
|
96
|
+
# newlines into spaces. Do not strip trailing quoted data. Callers of
|
|
97
|
+
# process_line may also supply lines without a terminating newline.
|
|
98
|
+
separator = previous_quote ? (@continued_line.end_with?("\n") ? '' : "\n") : ' '
|
|
99
|
+
line = @continued_line + separator + line
|
|
100
|
+
end
|
|
101
|
+
if @group_depth.positive?
|
|
102
|
+
@continued_line = line
|
|
103
|
+
return nil
|
|
104
|
+
end
|
|
105
|
+
@continued_line = nil
|
|
106
|
+
return nil if line.strip.empty?
|
|
86
107
|
|
|
87
108
|
if (line.index("$ORIGIN") == 0)
|
|
88
|
-
@origin =
|
|
109
|
+
@origin = absolute_name(Presentation.tokens(line)[1]).to_s(true) # $ORIGIN <domain-name>
|
|
89
110
|
# print "Setting $ORIGIN to #{@origin}\n"
|
|
90
111
|
return nil
|
|
91
112
|
end
|
|
92
113
|
if (line.index("$TTL") == 0)
|
|
93
|
-
@
|
|
114
|
+
@default_ttl = get_ttl(line.split()[1].strip) # $TTL <ttl>
|
|
94
115
|
# print "Setting $TTL to #{ttl}\n"
|
|
95
116
|
return nil
|
|
96
117
|
end
|
|
97
|
-
|
|
98
|
-
# Add the next line until we see a ")"
|
|
99
|
-
# REMEMBER TO STRIP OFF COMMENTS!!!
|
|
100
|
-
@continued_line = strip_comments(@continued_line)
|
|
101
|
-
line = @continued_line.rstrip.chomp + " " + line
|
|
102
|
-
if (line.index(")"))
|
|
103
|
-
# OK
|
|
104
|
-
@continued_line = false
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
open_bracket = line.index("(")
|
|
108
|
-
if (open_bracket)
|
|
109
|
-
# Keep going until we see ")"
|
|
110
|
-
index = line.index(")")
|
|
111
|
-
if (index && (index > open_bracket))
|
|
112
|
-
# OK
|
|
113
|
-
@continued_line = false
|
|
114
|
-
else
|
|
115
|
-
@continued_line = line
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
return nil if @continued_line
|
|
118
|
+
line += "\n"
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
120
|
+
# Preserve the public normalization hook for subclasses and prepends.
|
|
121
|
+
# Only unchanged input has already been scanned. Overrides may replace
|
|
122
|
+
# or mutate the String before calling super, introducing new syntax.
|
|
123
|
+
previous_scanned_line = @scanned_line
|
|
124
|
+
begin
|
|
125
|
+
@scanned_line = [line, line.dup]
|
|
126
|
+
normalise_line(line, do_prefix_hack)
|
|
127
|
+
ensure
|
|
128
|
+
@scanned_line = previous_scanned_line
|
|
129
|
+
end
|
|
129
130
|
end
|
|
130
131
|
|
|
131
132
|
def strip_comments(line)
|
|
@@ -209,52 +210,68 @@ module Dnsruby
|
|
|
209
210
|
# Take a line from the input zone file, and return the normalised form
|
|
210
211
|
# do_prefix_hack should always be false
|
|
211
212
|
def normalise_line(line, do_prefix_hack = false)
|
|
213
|
+
# Direct callers historically accepted unmatched grouping, just like
|
|
214
|
+
# RR.create. process_line has already scanned and checked its input.
|
|
215
|
+
unless @scanned_line && line.equal?(@scanned_line[0]) && line == @scanned_line[1]
|
|
216
|
+
single_quoted_start = if line.include?("'")
|
|
217
|
+
Presentation.single_quoted_start(line, owner_omitted: line.start_with?(' ', "\t"))
|
|
218
|
+
end
|
|
219
|
+
line, = Presentation.scan(line, allow_unbalanced: true, single_quoted_from: single_quoted_start)
|
|
220
|
+
end
|
|
221
|
+
normalise_scanned_line(line, do_prefix_hack)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def normalise_scanned_line(line, do_prefix_hack = false)
|
|
225
|
+
|
|
226
|
+
line = Presentation.escape_whitespace(line) if line.include?('\\')
|
|
227
|
+
|
|
212
228
|
# Note that a freestanding "@" is used to denote the current origin - we can simply replace that straight away
|
|
213
|
-
# Remove the ( and )
|
|
214
229
|
# Note that no domain name may be specified in the RR - in that case, last_name should be used. How do we tell? Tab or space at start of line.
|
|
215
230
|
|
|
216
231
|
# If we have text in the record, then ignore that in the parsing, and stick it on again at the end
|
|
217
232
|
stored_line = "";
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
233
|
+
# TXT quotes a whole token, but SVCB and HTTPS quote mid-token
|
|
234
|
+
# (alpn="h2,h3"), so remember which to put the text back as it came.
|
|
235
|
+
# A parenthesis separates too, and the strip below may remove it first.
|
|
236
|
+
stored_line_was_separate = true
|
|
237
|
+
quote_index = Presentation.unescaped_quote(line)
|
|
238
|
+
if line.include?("'") && (rdata_start = Presentation.single_quoted_start(line,
|
|
239
|
+
owner_omitted: line.start_with?(' ', "\t")))
|
|
240
|
+
quote_index = [quote_index, line.index(/(?<=[\s(])'/, rdata_start)].compact.min
|
|
241
|
+
end
|
|
242
|
+
if quote_index
|
|
243
|
+
stored_line_was_separate = (quote_index == 0) || !(/[\s()]/ =~ line[quote_index - 1]).nil?
|
|
244
|
+
stored_line = line[quote_index, line.length];
|
|
245
|
+
line = line [0, quote_index]
|
|
221
246
|
end
|
|
222
247
|
if ((line[0,1] == " ") || (line[0,1] == "\t"))
|
|
223
248
|
line = @last_name + " " + line
|
|
224
249
|
end
|
|
225
250
|
line.chomp!
|
|
226
|
-
line.sub!(/\s
|
|
227
|
-
line.sub!(/^@\s
|
|
228
|
-
line.sub!(/\s+@\s
|
|
251
|
+
line.sub!(/\s+@$/) { " #{@origin}" } # IN CNAME @
|
|
252
|
+
line.sub!(/^@\s+/) { "#{@origin} " } # IN CNAME @
|
|
253
|
+
line.sub!(/\s+@\s+/) { " #{@origin} " }
|
|
229
254
|
line.strip!
|
|
230
255
|
|
|
231
256
|
|
|
232
257
|
# o We need to identify the domain name in the record, and then
|
|
233
|
-
split =
|
|
234
|
-
name = split[0]
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
new_name = ls.join('.')
|
|
240
|
-
|
|
258
|
+
split = Presentation.tokens(line)
|
|
259
|
+
name = split[0]
|
|
260
|
+
new_name = absolute_name(name).to_s(true)
|
|
261
|
+
line.sub!(name) { new_name }
|
|
262
|
+
name = new_name
|
|
263
|
+
split = line.split
|
|
241
264
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
265
|
+
# RFC 1035 section 5.1 permits either TTL/class or class/TTL.
|
|
266
|
+
# Canonicalize the latter before applying the inherited defaults below.
|
|
267
|
+
if split[2] && /\A(?:[0-9]+[smhdwSMHDW])*[0-9]*\z/.match?(split[2]) && !split[2].empty?
|
|
268
|
+
begin
|
|
269
|
+
Classes.new(split[1])
|
|
270
|
+
split[1], split[2] = split[2], split[1]
|
|
271
|
+
line = split.join(' ') + "\n"
|
|
272
|
+
rescue ArgumentError
|
|
273
|
+
# The second token is a TTL or record type, not a class.
|
|
246
274
|
end
|
|
247
|
-
line = new_name + " "
|
|
248
|
-
(split.length - 1).times {|i| line += "#{split[i+1]} "}
|
|
249
|
-
line += "\n"
|
|
250
|
-
name = new_name
|
|
251
|
-
split = line.split
|
|
252
|
-
# o add $ORIGIN to it if it is not absolute
|
|
253
|
-
elsif !(/\.\z/ =~ name)
|
|
254
|
-
new_name = name + "." + @origin
|
|
255
|
-
line.sub!(name, new_name)
|
|
256
|
-
name = new_name
|
|
257
|
-
split = line.split
|
|
258
275
|
end
|
|
259
276
|
|
|
260
277
|
# If the second field is not a number, then we should add the TTL to the line
|
|
@@ -270,7 +287,8 @@ module Dnsruby
|
|
|
270
287
|
split = line.split
|
|
271
288
|
elsif (((split[1]).to_i == 0) && (split[1] != "0"))
|
|
272
289
|
# Add the TTL
|
|
273
|
-
|
|
290
|
+
inherited_ttl = @default_ttl || @last_explicit_ttl
|
|
291
|
+
if (!inherited_ttl)
|
|
274
292
|
# If this is the SOA record, and no @last_explicit_ttl is defined,
|
|
275
293
|
# then we need to try the SOA TTL element from the config. Otherwise,
|
|
276
294
|
# find the SOA Minimum field, and use that.
|
|
@@ -281,7 +299,7 @@ module Dnsruby
|
|
|
281
299
|
# the actual type. If the type is not SOA then, then we can raise an error
|
|
282
300
|
line = name + " %MISSING_TTL% "
|
|
283
301
|
else
|
|
284
|
-
line = name + " #{
|
|
302
|
+
line = name + " #{inherited_ttl} "
|
|
285
303
|
end
|
|
286
304
|
(split.length - 1).times {|i| line += "#{split[i+1]} "}
|
|
287
305
|
line += "\n"
|
|
@@ -292,7 +310,11 @@ module Dnsruby
|
|
|
292
310
|
|
|
293
311
|
# Now see if the clas is included. If not, then we should default to the last class used.
|
|
294
312
|
begin
|
|
295
|
-
|
|
313
|
+
class_text = split[2]
|
|
314
|
+
if /\ACLASS\d+\z/.match?(class_text)
|
|
315
|
+
class_text = Classes.classesbyval(Classes.classesbyname(class_text))
|
|
316
|
+
end
|
|
317
|
+
klass = Classes.new(class_text)
|
|
296
318
|
@last_explicit_class = klass
|
|
297
319
|
rescue ArgumentError
|
|
298
320
|
# Wasn't a CLASS
|
|
@@ -307,22 +329,28 @@ module Dnsruby
|
|
|
307
329
|
end
|
|
308
330
|
|
|
309
331
|
# Add the type so we can load the zone one RRSet at a time.
|
|
310
|
-
|
|
332
|
+
type_text = split[3].strip
|
|
333
|
+
if /\ATYPE\d+\z/.match?(type_text)
|
|
334
|
+
type_text = Types.typesbyval(Types.typesbyname(type_text))
|
|
335
|
+
end
|
|
336
|
+
type = Types.new(type_text)
|
|
311
337
|
is_soa = (type == Types::SOA)
|
|
312
338
|
type_was = type
|
|
339
|
+
# RFC 3597 RDATA is already encoded wire data, even for known types.
|
|
340
|
+
# Its tokens must not be treated as textual names or SOA intervals.
|
|
341
|
+
generic_rdata = split[4]&.start_with?('\\#')
|
|
313
342
|
if (type == Types.RRSIG)
|
|
314
343
|
# If this is an RRSIG record, then add the TYPE COVERED rather than the type - this allows us to load a complete RRSet at a time
|
|
315
|
-
|
|
344
|
+
if generic_rdata
|
|
345
|
+
type = Dnsruby::RR.send(:new_from_normalised_string, line).type_covered if do_prefix_hack
|
|
346
|
+
else
|
|
347
|
+
type = Types.new(split[4].strip)
|
|
348
|
+
end
|
|
316
349
|
end
|
|
317
350
|
|
|
318
351
|
type_string=prefix_for_rrset_order(type, type_was)
|
|
319
352
|
@last_name = name
|
|
320
353
|
|
|
321
|
-
if !([Types::NAPTR, Types::TXT].include?type_was)
|
|
322
|
-
line.sub!("(", "")
|
|
323
|
-
line.sub!(")", "")
|
|
324
|
-
end
|
|
325
|
-
|
|
326
354
|
if (is_soa)
|
|
327
355
|
if (@soa_ttl)
|
|
328
356
|
# Replace the %MISSING_TTL% text with the SOA TTL from the config
|
|
@@ -333,9 +361,9 @@ module Dnsruby
|
|
|
333
361
|
line.sub!(" %MISSING_TTL% ", " #{@last_explicit_ttl} ")
|
|
334
362
|
end
|
|
335
363
|
end
|
|
336
|
-
line = replace_soa_ttl_fields(line)
|
|
364
|
+
line = replace_soa_ttl_fields(line) unless generic_rdata
|
|
337
365
|
if (!@last_explicit_ttl)
|
|
338
|
-
soa_rr = Dnsruby::RR.
|
|
366
|
+
soa_rr = Dnsruby::RR.send(:new_from_normalised_string, line)
|
|
339
367
|
@last_explicit_ttl = soa_rr.minimum
|
|
340
368
|
end
|
|
341
369
|
end
|
|
@@ -343,47 +371,66 @@ module Dnsruby
|
|
|
343
371
|
line = line.strip
|
|
344
372
|
|
|
345
373
|
if (stored_line && stored_line != "")
|
|
346
|
-
line += " "
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if
|
|
358
|
-
|
|
374
|
+
line += " " if stored_line_was_separate
|
|
375
|
+
line += stored_line.strip
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
if generic_rdata
|
|
379
|
+
return line + "\n", type_string, @last_name if do_prefix_hack
|
|
380
|
+
return line + "\n"
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
if [Types::TXT, Types::SPF].include?(type_was) && line.include?('\\')
|
|
384
|
+
header_and_data = line.split(/\s+/, 5)
|
|
385
|
+
if header_and_data[4]
|
|
386
|
+
header_and_data[4] = Presentation.character_string_escapes(header_and_data[4])
|
|
387
|
+
line = header_and_data.join(' ')
|
|
359
388
|
end
|
|
360
389
|
end
|
|
361
|
-
|
|
362
|
-
#
|
|
363
|
-
if
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
390
|
+
|
|
391
|
+
# Qualify parsed labels so an escaped final dot stays inside its label.
|
|
392
|
+
if [Types::MX, Types::KX, Types::NS, Types::AFSDB, Types::NAPTR, Types::RT,
|
|
393
|
+
Types::SRV, Types::CNAME, Types::MB, Types::MG, Types::MR,
|
|
394
|
+
Types::PTR, Types::DNAME].include?(type_was)
|
|
395
|
+
line = line.sub(/\S+\z/) { |target| absolute_name(target).to_s(true) }
|
|
396
|
+
end
|
|
397
|
+
name_fields = case type_was
|
|
398
|
+
when Types::SOA then [:mname, :rname]
|
|
399
|
+
when Types::MINFO then [:rmailbx, :emailbx]
|
|
400
|
+
when Types::PX then [:map822, :mapx400]
|
|
401
|
+
when Types::RP then [:mailbox, :txtdomain]
|
|
402
|
+
end
|
|
403
|
+
if name_fields
|
|
404
|
+
parsed_rr = Dnsruby::RR.send(:new_from_normalised_string, line)
|
|
405
|
+
name_fields.each do |field|
|
|
406
|
+
parsed_rr.public_send("#{field}=", absolute_name(parsed_rr.public_send(field)))
|
|
407
|
+
end
|
|
408
|
+
line = parsed_rr.to_s
|
|
409
|
+
end
|
|
410
|
+
# These name fields precede other RDATA. Qualify their presentation
|
|
411
|
+
# tokens directly, preserving the remaining signature/key text and
|
|
412
|
+
# avoiding record serializers which omit a name's trailing dot.
|
|
413
|
+
case type_was
|
|
414
|
+
when Types::NSEC, Types::NXT, Types::RRSIG, Types::IPSECKEY, Types::HIP
|
|
415
|
+
fields = line.split
|
|
416
|
+
domain_fields = case type_was
|
|
417
|
+
when Types::NSEC, Types::NXT then [4]
|
|
418
|
+
when Types::RRSIG then [11]
|
|
419
|
+
when Types::IPSECKEY then fields[5].to_i == 3 ? [7] : []
|
|
420
|
+
when Types::HIP then (7...fields.length).to_a
|
|
421
|
+
end
|
|
422
|
+
domain_fields.each { |index| fields[index] = absolute_name(fields.fetch(index)).to_s(true) }
|
|
423
|
+
line = fields.join(' ')
|
|
424
|
+
end
|
|
425
|
+
# SVCB and HTTPS carry their TargetName in the middle of the RDATA
|
|
426
|
+
# (before the SvcParams), so the trailing-name logic above cannot reach
|
|
427
|
+
# it. Parse the record and qualify a relative TargetName against the
|
|
428
|
+
# origin; the root (".") is absolute, so the absolute? guard skips it.
|
|
429
|
+
if ([Types::SVCB, Types::HTTPS].include?type_was)
|
|
430
|
+
parsed_rr = Dnsruby::RR.send(:new_from_normalised_string, line)
|
|
431
|
+
if parsed_rr.is_a?(RR::SVCB) && parsed_rr.target && !parsed_rr.target.absolute?
|
|
432
|
+
origin = RR::SVCB.create_target_name(@origin.to_s)
|
|
433
|
+
parsed_rr.target = Name.new(parsed_rr.target.labels + origin.labels, true)
|
|
387
434
|
end
|
|
388
435
|
line = parsed_rr.to_s
|
|
389
436
|
end
|
|
@@ -392,6 +439,25 @@ module Dnsruby
|
|
|
392
439
|
end
|
|
393
440
|
return line+"\n"
|
|
394
441
|
end
|
|
442
|
+
private :normalise_scanned_line
|
|
443
|
+
|
|
444
|
+
def absolute_name(value)
|
|
445
|
+
name = parse_zone_name(value)
|
|
446
|
+
return name if name.absolute?
|
|
447
|
+
Name.new(name.labels + parse_zone_name(@origin).labels, true)
|
|
448
|
+
end
|
|
449
|
+
private :absolute_name
|
|
450
|
+
|
|
451
|
+
# Zone-file ASCII already represents wire octets. Preserve its case for
|
|
452
|
+
# record types such as SVCB whose signed TargetName is not case-folded.
|
|
453
|
+
def parse_zone_name(value)
|
|
454
|
+
if value.is_a?(String) && value.ascii_only?
|
|
455
|
+
Name.new(Name.split_escaped(value), /(?<!\\)(?:\\\\)*\.\z/.match?(value))
|
|
456
|
+
else
|
|
457
|
+
Name.create(value)
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
private :parse_zone_name
|
|
395
461
|
|
|
396
462
|
# Get the TTL in seconds from the m, h, d, w format
|
|
397
463
|
def get_ttl(ttl_text_in)
|
|
@@ -410,15 +476,15 @@ module Dnsruby
|
|
|
410
476
|
number = ttl_text[last_index + 1, index-last_index-1].to_i
|
|
411
477
|
new_number = 0
|
|
412
478
|
case letter
|
|
413
|
-
when
|
|
479
|
+
when "s"
|
|
414
480
|
new_number = number
|
|
415
|
-
when
|
|
481
|
+
when "m"
|
|
416
482
|
new_number = number * 60
|
|
417
|
-
when
|
|
483
|
+
when "h"
|
|
418
484
|
new_number = number * 3600
|
|
419
|
-
when
|
|
485
|
+
when "d"
|
|
420
486
|
new_number = number * 86400
|
|
421
|
-
when
|
|
487
|
+
when "w"
|
|
422
488
|
new_number = number * 604800
|
|
423
489
|
end
|
|
424
490
|
total += new_number
|
|
@@ -426,6 +492,7 @@ module Dnsruby
|
|
|
426
492
|
last_index = index
|
|
427
493
|
index = ttl_text.index(/[whdms]/, last_index + 1)
|
|
428
494
|
end
|
|
495
|
+
total += ttl_text[last_index + 1..].to_i
|
|
429
496
|
return total
|
|
430
497
|
end
|
|
431
498
|
|