dnsruby 1.55 → 1.56.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/README.md +96 -0
- data/Rakefile +30 -29
- data/demo/axfr.rb +93 -93
- data/demo/check_soa.rb +99 -99
- data/demo/check_zone.rb +59 -59
- data/demo/digdlv.rb +43 -43
- data/demo/digroot.rb +34 -34
- data/demo/example_recurse.rb +14 -14
- data/demo/mresolv.rb +30 -30
- data/demo/mx.rb +31 -31
- data/demo/rubydig.rb +37 -37
- data/demo/to_resolve.txt +3088 -3088
- data/demo/trace_dns.rb +46 -46
- data/lib/dnsruby.rb +161 -526
- data/lib/dnsruby/DNS.rb +305 -0
- data/lib/{Dnsruby/Cache.rb → dnsruby/cache.rb} +152 -152
- data/lib/{Dnsruby → dnsruby}/code_mapper.rb +48 -52
- data/lib/dnsruby/code_mappers.rb +295 -0
- data/lib/{Dnsruby/Config.rb → dnsruby/config.rb} +454 -454
- data/lib/{Dnsruby → dnsruby}/dnssec.rb +91 -91
- data/lib/{Dnsruby/Hosts.rb → dnsruby/hosts.rb} +125 -125
- data/lib/{Dnsruby → dnsruby}/ipv4.rb +26 -26
- data/lib/{Dnsruby → dnsruby}/ipv6.rb +42 -42
- data/lib/{Dnsruby → dnsruby}/key_cache.rb +29 -29
- data/lib/dnsruby/message/decoder.rb +164 -0
- data/lib/dnsruby/message/encoder.rb +75 -0
- data/lib/dnsruby/message/header.rb +249 -0
- data/lib/dnsruby/message/message.rb +629 -0
- data/lib/dnsruby/message/question.rb +86 -0
- data/lib/dnsruby/message/section.rb +96 -0
- data/lib/{Dnsruby → dnsruby}/name.rb +141 -141
- data/lib/dnsruby/packet_sender.rb +661 -0
- data/lib/{Dnsruby/Recursor.rb → dnsruby/recursor.rb} +235 -233
- data/lib/dnsruby/resolv.rb +113 -0
- data/lib/dnsruby/resolver.rb +1192 -0
- data/lib/dnsruby/resource/A.rb +56 -0
- data/lib/dnsruby/resource/AAAA.rb +54 -0
- data/lib/{Dnsruby → dnsruby}/resource/AFSDB.rb +68 -68
- data/lib/{Dnsruby → dnsruby}/resource/CERT.rb +105 -105
- data/lib/{Dnsruby → dnsruby}/resource/DHCID.rb +54 -54
- data/lib/dnsruby/resource/DLV.rb +27 -0
- data/lib/{Dnsruby → dnsruby}/resource/DNSKEY.rb +372 -372
- data/lib/{Dnsruby → dnsruby}/resource/DS.rb +255 -255
- data/lib/{Dnsruby → dnsruby}/resource/HINFO.rb +71 -71
- data/lib/{Dnsruby → dnsruby}/resource/HIP.rb +29 -29
- data/lib/{Dnsruby → dnsruby}/resource/IN.rb +30 -30
- data/lib/{Dnsruby → dnsruby}/resource/IPSECKEY.rb +31 -31
- data/lib/{Dnsruby → dnsruby}/resource/ISDN.rb +62 -62
- data/lib/{Dnsruby → dnsruby}/resource/KX.rb +65 -65
- data/lib/{Dnsruby → dnsruby}/resource/LOC.rb +263 -263
- data/lib/{Dnsruby → dnsruby}/resource/MINFO.rb +69 -69
- data/lib/{Dnsruby → dnsruby}/resource/MX.rb +65 -65
- data/lib/{Dnsruby → dnsruby}/resource/NAPTR.rb +98 -98
- data/lib/{Dnsruby → dnsruby}/resource/NSAP.rb +171 -171
- data/lib/dnsruby/resource/NSEC.rb +275 -0
- data/lib/dnsruby/resource/NSEC3.rb +332 -0
- data/lib/dnsruby/resource/NSEC3PARAM.rb +135 -0
- data/lib/dnsruby/resource/OPT.rb +272 -0
- data/lib/{Dnsruby → dnsruby}/resource/PX.rb +70 -70
- data/lib/{Dnsruby → dnsruby}/resource/RP.rb +75 -75
- data/lib/dnsruby/resource/RR.rb +421 -0
- data/lib/dnsruby/resource/RRSIG.rb +275 -0
- data/lib/dnsruby/resource/RRSet.rb +190 -0
- data/lib/{Dnsruby → dnsruby}/resource/RT.rb +67 -67
- data/lib/{Dnsruby → dnsruby}/resource/SOA.rb +94 -94
- data/lib/dnsruby/resource/SPF.rb +29 -0
- data/lib/dnsruby/resource/SRV.rb +112 -0
- data/lib/{Dnsruby → dnsruby}/resource/SSHFP.rb +14 -14
- data/lib/dnsruby/resource/TKEY.rb +163 -0
- data/lib/dnsruby/resource/TSIG.rb +593 -0
- data/lib/{Dnsruby → dnsruby}/resource/TXT.rb +191 -191
- data/lib/dnsruby/resource/X25.rb +55 -0
- data/lib/{Dnsruby → dnsruby}/resource/domain_name.rb +25 -25
- data/lib/{Dnsruby → dnsruby}/resource/generic.rb +80 -80
- data/lib/dnsruby/resource/resource.rb +25 -0
- data/lib/{Dnsruby → dnsruby}/select_thread.rb +148 -148
- data/lib/{Dnsruby/SingleResolver.rb → dnsruby/single_resolver.rb} +60 -60
- data/lib/{Dnsruby → dnsruby}/single_verifier.rb +344 -344
- data/lib/dnsruby/the_log.rb +44 -0
- data/lib/dnsruby/update.rb +278 -0
- data/lib/dnsruby/validator_thread.rb +124 -0
- data/lib/dnsruby/version.rb +3 -0
- data/lib/{Dnsruby → dnsruby}/zone_reader.rb +93 -93
- data/lib/{Dnsruby → dnsruby}/zone_transfer.rb +377 -377
- data/test/spec_helper.rb +16 -0
- data/test/tc_axfr.rb +31 -34
- data/test/tc_cache.rb +32 -32
- data/test/tc_dlv.rb +28 -28
- data/test/tc_dns.rb +73 -76
- data/test/tc_dnskey.rb +31 -32
- data/test/tc_dnsruby.rb +50 -44
- data/test/tc_ds.rb +36 -36
- data/test/tc_escapedchars.rb +252 -255
- data/test/tc_hash.rb +17 -21
- data/test/tc_header.rb +48 -57
- data/test/tc_hip.rb +19 -22
- data/test/tc_ipseckey.rb +18 -21
- data/test/tc_keith.rb +300 -0
- data/test/tc_message.rb +87 -0
- data/test/tc_misc.rb +83 -87
- data/test/tc_name.rb +81 -84
- data/test/tc_naptr.rb +18 -21
- data/test/tc_nsec.rb +55 -55
- data/test/tc_nsec3.rb +23 -24
- data/test/tc_nsec3param.rb +20 -21
- data/test/tc_packet.rb +90 -93
- data/test/tc_packet_unique_push.rb +48 -51
- data/test/tc_question.rb +30 -33
- data/test/tc_queue.rb +16 -17
- data/test/tc_recur.rb +16 -17
- data/test/tc_res_config.rb +38 -41
- data/test/tc_res_env.rb +29 -32
- data/test/tc_res_file.rb +26 -29
- data/test/tc_res_opt.rb +62 -65
- data/test/tc_resolver.rb +287 -242
- data/test/tc_rr-opt.rb +70 -63
- data/test/tc_rr-txt.rb +68 -71
- data/test/tc_rr-unknown.rb +45 -48
- data/test/tc_rr.rb +76 -70
- data/test/tc_rrset.rb +21 -22
- data/test/tc_rrsig.rb +19 -20
- data/test/tc_single_resolver.rb +294 -297
- data/test/tc_soak.rb +199 -202
- data/test/tc_soak_base.rb +29 -34
- data/test/tc_sshfp.rb +20 -23
- data/test/tc_tcp.rb +32 -35
- data/test/tc_tkey.rb +41 -44
- data/test/tc_tsig.rb +81 -84
- data/test/tc_update.rb +108 -111
- data/test/tc_validator.rb +29 -29
- data/test/tc_verifier.rb +81 -82
- data/test/ts_dnsruby.rb +16 -15
- data/test/ts_offline.rb +62 -63
- data/test/ts_online.rb +115 -115
- metadata +155 -90
- data/README +0 -59
- data/lib/Dnsruby/DNS.rb +0 -305
- data/lib/Dnsruby/PacketSender.rb +0 -656
- data/lib/Dnsruby/Resolver.rb +0 -1189
- data/lib/Dnsruby/TheLog.rb +0 -44
- data/lib/Dnsruby/message.rb +0 -1230
- data/lib/Dnsruby/resource/A.rb +0 -56
- data/lib/Dnsruby/resource/AAAA.rb +0 -54
- data/lib/Dnsruby/resource/DLV.rb +0 -27
- data/lib/Dnsruby/resource/NSEC.rb +0 -298
- data/lib/Dnsruby/resource/NSEC3.rb +0 -340
- data/lib/Dnsruby/resource/NSEC3PARAM.rb +0 -135
- data/lib/Dnsruby/resource/OPT.rb +0 -213
- data/lib/Dnsruby/resource/RRSIG.rb +0 -275
- data/lib/Dnsruby/resource/SPF.rb +0 -29
- data/lib/Dnsruby/resource/SRV.rb +0 -112
- data/lib/Dnsruby/resource/TKEY.rb +0 -163
- data/lib/Dnsruby/resource/TSIG.rb +0 -593
- data/lib/Dnsruby/resource/X25.rb +0 -55
- data/lib/Dnsruby/resource/resource.rb +0 -678
- data/lib/Dnsruby/update.rb +0 -278
- data/lib/Dnsruby/validator_thread.rb +0 -124
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
#Copyright 2009 Nominet UK
|
|
3
|
-
#
|
|
4
|
-
#Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
#you may not use this file except in compliance with the License.
|
|
6
|
-
#You may obtain a copy of the License at
|
|
7
|
-
#
|
|
1
|
+
# --
|
|
2
|
+
# Copyright 2009 Nominet UK
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
8
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
#
|
|
10
|
-
#Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
#distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
#See the License for the specific language governing permissions and
|
|
14
|
-
#limitations under the License.
|
|
15
|
-
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
# ++
|
|
16
16
|
|
|
17
17
|
# This class provides the facility to load a zone file.
|
|
18
18
|
# It can either process one line at a time, or return an entire zone as a list of
|
|
@@ -22,8 +22,8 @@ module Dnsruby
|
|
|
22
22
|
class ParseException < Exception
|
|
23
23
|
|
|
24
24
|
end
|
|
25
|
-
#
|
|
26
|
-
#
|
|
25
|
+
# Create a new ZoneReader. The zone origin is required. If the desired SOA minimum
|
|
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
28
|
@origin = origin.to_s
|
|
29
29
|
|
|
@@ -42,8 +42,8 @@ module Dnsruby
|
|
|
42
42
|
@in_quoted_section = false
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
#
|
|
46
|
-
#
|
|
45
|
+
# Takes a filename string and attempts to load a zone. Returns a list
|
|
46
|
+
# of RRs if successful, nil otherwise.
|
|
47
47
|
def process_file(file)
|
|
48
48
|
line_num = 0
|
|
49
49
|
zone = nil
|
|
@@ -65,8 +65,8 @@ module Dnsruby
|
|
|
65
65
|
return zone
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
#
|
|
69
|
-
#
|
|
68
|
+
# Process the next line of the file
|
|
69
|
+
# Returns a string representing the normalised line.
|
|
70
70
|
def process_line(line, do_prefix_hack = false)
|
|
71
71
|
return nil if (line[0,1] == ";")
|
|
72
72
|
return nil if (line.strip.length == 0)
|
|
@@ -77,30 +77,30 @@ module Dnsruby
|
|
|
77
77
|
|
|
78
78
|
if (line.index("$ORIGIN") == 0)
|
|
79
79
|
@origin = line.split()[1].strip # $ORIGIN <domain-name> [<comment>]
|
|
80
|
-
#
|
|
80
|
+
# print "Setting $ORIGIN to #{@origin}\n"
|
|
81
81
|
return nil
|
|
82
82
|
end
|
|
83
83
|
if (line.index("$TTL") == 0)
|
|
84
84
|
@last_explicit_ttl = get_ttl(line.split()[1].strip) # $TTL <ttl>
|
|
85
|
-
#
|
|
85
|
+
# print "Setting $TTL to #{ttl}\n"
|
|
86
86
|
return nil
|
|
87
87
|
end
|
|
88
88
|
if (@continued_line)
|
|
89
|
-
#
|
|
90
|
-
#
|
|
89
|
+
# Add the next line until we see a ")"
|
|
90
|
+
# REMEMBER TO STRIP OFF COMMENTS!!!
|
|
91
91
|
@continued_line = strip_comments(@continued_line)
|
|
92
92
|
line = @continued_line.rstrip.chomp + " " + line
|
|
93
93
|
if (line.index(")"))
|
|
94
|
-
#
|
|
94
|
+
# OK
|
|
95
95
|
@continued_line = false
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
open_bracket = line.index("(")
|
|
99
99
|
if (open_bracket)
|
|
100
|
-
#
|
|
100
|
+
# Keep going until we see ")"
|
|
101
101
|
index = line.index(")")
|
|
102
102
|
if (index && (index > open_bracket))
|
|
103
|
-
#
|
|
103
|
+
# OK
|
|
104
104
|
@continued_line = false
|
|
105
105
|
else
|
|
106
106
|
@continued_line = line
|
|
@@ -110,45 +110,45 @@ module Dnsruby
|
|
|
110
110
|
|
|
111
111
|
line = strip_comments(line) + "\n"
|
|
112
112
|
|
|
113
|
-
#
|
|
114
|
-
#
|
|
113
|
+
# If SOA, then replace "3h" etc. with expanded seconds
|
|
114
|
+
# begin
|
|
115
115
|
return normalise_line(line, do_prefix_hack)
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
116
|
+
# rescue Exception => e
|
|
117
|
+
# print "ERROR parsing line #{@line_num} : #{line}\n"
|
|
118
|
+
# return "\n", Types::ANY
|
|
119
|
+
# end
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
def strip_comments(line)
|
|
123
123
|
last_index = 0
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
124
|
+
# Are we currently in a quoted section?
|
|
125
|
+
# Does a quoted section begin or end in this line?
|
|
126
|
+
# Are there any semi-colons?
|
|
127
|
+
# Ary any of the semi-colons inside a quoted section?
|
|
128
|
+
# Handle escape characters
|
|
129
129
|
if (line.index"\\")
|
|
130
130
|
return strip_comments_meticulously(line)
|
|
131
131
|
end
|
|
132
132
|
while (next_index = line.index(";", last_index + 1))
|
|
133
|
-
#
|
|
133
|
+
# Have there been any quotes since we last looked?
|
|
134
134
|
process_quotes(line[last_index, next_index - last_index])
|
|
135
135
|
|
|
136
|
-
#
|
|
136
|
+
# Now use @in_quoted_section to work out if the ';' terminates the line
|
|
137
137
|
if (!@in_quoted_section)
|
|
138
138
|
return line[0,next_index]
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
last_index = next_index
|
|
142
142
|
end
|
|
143
|
-
#
|
|
143
|
+
# Check out the quote situation to the end of the line
|
|
144
144
|
process_quotes(line[last_index, line.length-1])
|
|
145
145
|
|
|
146
146
|
return line
|
|
147
147
|
end
|
|
148
148
|
|
|
149
149
|
def strip_comments_meticulously(line)
|
|
150
|
-
#
|
|
151
|
-
#
|
|
150
|
+
# We have escape characters in the text. Go through it character by
|
|
151
|
+
# character and work out what's escaped and quoted and what's not
|
|
152
152
|
escaped = false
|
|
153
153
|
quoted = false
|
|
154
154
|
pos = 0
|
|
@@ -188,8 +188,8 @@ module Dnsruby
|
|
|
188
188
|
end
|
|
189
189
|
|
|
190
190
|
def process_quotes(section)
|
|
191
|
-
#
|
|
192
|
-
#
|
|
191
|
+
# Look through the section of text and set the @in_quoted_section
|
|
192
|
+
# as it should be at the end of the given section
|
|
193
193
|
last_index = 0
|
|
194
194
|
while (next_index = section.index("\"", last_index + 1))
|
|
195
195
|
@in_quoted_section = !@in_quoted_section
|
|
@@ -197,14 +197,14 @@ module Dnsruby
|
|
|
197
197
|
end
|
|
198
198
|
end
|
|
199
199
|
|
|
200
|
-
#
|
|
201
|
-
#
|
|
200
|
+
# Take a line from the input zone file, and return the normalised form
|
|
201
|
+
# do_prefix_hack should always be false
|
|
202
202
|
def normalise_line(line, do_prefix_hack = false)
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
203
|
+
# Note that a freestanding "@" is used to denote the current origin - we can simply replace that straight away
|
|
204
|
+
# Remove the ( and )
|
|
205
|
+
# 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.
|
|
206
206
|
|
|
207
|
-
#
|
|
207
|
+
# If we have text in the record, then ignore that in the parsing, and stick it on again at the end
|
|
208
208
|
stored_line = "";
|
|
209
209
|
if (line.index('"') != nil)
|
|
210
210
|
stored_line = line[line.index('"'), line.length];
|
|
@@ -220,11 +220,11 @@ module Dnsruby
|
|
|
220
220
|
line.strip!
|
|
221
221
|
|
|
222
222
|
|
|
223
|
-
#
|
|
223
|
+
# o We need to identify the domain name in the record, and then
|
|
224
224
|
split = line.split(' ') # split on whitespace
|
|
225
225
|
name = split[0].strip
|
|
226
226
|
if (name.index"\\")
|
|
227
|
-
|
|
227
|
+
|
|
228
228
|
ls =[]
|
|
229
229
|
Name.create(name).labels.each {|el| ls.push(Name.decode(el.to_s))}
|
|
230
230
|
new_name = ls.join('.')
|
|
@@ -240,7 +240,7 @@ module Dnsruby
|
|
|
240
240
|
line += "\n"
|
|
241
241
|
name = new_name
|
|
242
242
|
split = line.split
|
|
243
|
-
#
|
|
243
|
+
# o add $ORIGIN to it if it is not absolute
|
|
244
244
|
elsif !(/\.\z/ =~ name)
|
|
245
245
|
new_name = name + "." + @origin
|
|
246
246
|
line.sub!(name, new_name)
|
|
@@ -248,11 +248,11 @@ module Dnsruby
|
|
|
248
248
|
split = line.split
|
|
249
249
|
end
|
|
250
250
|
|
|
251
|
-
#
|
|
252
|
-
#
|
|
251
|
+
# If the second field is not a number, then we should add the TTL to the line
|
|
252
|
+
# Remember we can get "m" "w" "y" here! So need to check for appropriate regexp...
|
|
253
253
|
found_ttl_regexp = (split[1]=~/^[0-9]+[smhdwSMHDW]/)
|
|
254
254
|
if (found_ttl_regexp == 0)
|
|
255
|
-
#
|
|
255
|
+
# Replace the formatted ttl with an actual number
|
|
256
256
|
ttl = get_ttl(split[1])
|
|
257
257
|
line = name + " #{ttl} "
|
|
258
258
|
@last_explicit_ttl = ttl
|
|
@@ -260,16 +260,16 @@ module Dnsruby
|
|
|
260
260
|
line += "\n"
|
|
261
261
|
split = line.split
|
|
262
262
|
elsif (((split[1]).to_i == 0) && (split[1] != "0"))
|
|
263
|
-
#
|
|
263
|
+
# Add the TTL
|
|
264
264
|
if (!@last_explicit_ttl)
|
|
265
|
-
#
|
|
266
|
-
#
|
|
267
|
-
#
|
|
268
|
-
#
|
|
269
|
-
#
|
|
270
|
-
#
|
|
271
|
-
#
|
|
272
|
-
#
|
|
265
|
+
# If this is the SOA record, and no @last_explicit_ttl is defined,
|
|
266
|
+
# then we need to try the SOA TTL element from the config. Otherwise,
|
|
267
|
+
# find the SOA Minimum field, and use that.
|
|
268
|
+
# We should also generate a warning to that effect
|
|
269
|
+
# How do we know if it is an SOA record at this stage? It must be, or
|
|
270
|
+
# else @last_explicit_ttl should be defined
|
|
271
|
+
# We could put a marker in the RR for now - and replace it once we know
|
|
272
|
+
# the actual type. If the type is not SOA then, then we can raise an error
|
|
273
273
|
line = name + " %MISSING_TTL% "
|
|
274
274
|
else
|
|
275
275
|
line = name + " #{@last_explicit_ttl} "
|
|
@@ -281,13 +281,13 @@ module Dnsruby
|
|
|
281
281
|
@last_explicit_ttl = split[1].to_i
|
|
282
282
|
end
|
|
283
283
|
|
|
284
|
-
#
|
|
284
|
+
# Now see if the clas is included. If not, then we should default to the last class used.
|
|
285
285
|
begin
|
|
286
286
|
klass = Classes.new(split[2])
|
|
287
287
|
@last_explicit_class = klass
|
|
288
288
|
rescue ArgumentError
|
|
289
|
-
#
|
|
290
|
-
#
|
|
289
|
+
# Wasn't a CLASS
|
|
290
|
+
# So add the last explicit class in
|
|
291
291
|
line = ""
|
|
292
292
|
(2).times {|i| line += "#{split[i]} "}
|
|
293
293
|
line += " #{@last_explicit_class} "
|
|
@@ -297,12 +297,12 @@ module Dnsruby
|
|
|
297
297
|
rescue Error => e
|
|
298
298
|
end
|
|
299
299
|
|
|
300
|
-
#
|
|
300
|
+
# Add the type so we can load the zone one RRSet at a time.
|
|
301
301
|
type = Types.new(split[3].strip)
|
|
302
302
|
is_soa = (type == Types::SOA)
|
|
303
303
|
type_was = type
|
|
304
304
|
if (type == Types.RRSIG)
|
|
305
|
-
#
|
|
305
|
+
# 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
|
|
306
306
|
type = Types.new(split[4].strip)
|
|
307
307
|
end
|
|
308
308
|
|
|
@@ -316,10 +316,10 @@ module Dnsruby
|
|
|
316
316
|
|
|
317
317
|
if (is_soa)
|
|
318
318
|
if (@soa_ttl)
|
|
319
|
-
#
|
|
319
|
+
# Replace the %MISSING_TTL% text with the SOA TTL from the config
|
|
320
320
|
line.sub!(" %MISSING_TTL% ", " #{@soa_ttl} ")
|
|
321
321
|
else
|
|
322
|
-
#
|
|
322
|
+
# Can we try the @last_explicit_ttl?
|
|
323
323
|
if (@last_explicit_ttl)
|
|
324
324
|
line.sub!(" %MISSING_TTL% ", " #{@last_explicit_ttl} ")
|
|
325
325
|
end
|
|
@@ -337,20 +337,20 @@ module Dnsruby
|
|
|
337
337
|
line += " " + stored_line.strip
|
|
338
338
|
end
|
|
339
339
|
|
|
340
|
-
#
|
|
341
|
-
#
|
|
342
|
-
#
|
|
343
|
-
#
|
|
340
|
+
# We need to fix up any non-absolute names in the RR
|
|
341
|
+
# Some RRs have a single name, at the end of the string -
|
|
342
|
+
# to do these, we can just check the last character for "." and add the
|
|
343
|
+
# "." + origin string if necessary
|
|
344
344
|
if ([Types::MX, Types::NS, Types::AFSDB, Types::NAPTR, Types::RT,
|
|
345
345
|
Types::SRV, Types::CNAME, Types::MB, Types::MG, Types::MR,
|
|
346
346
|
Types::PTR, Types::DNAME].include?type_was)
|
|
347
|
-
#
|
|
347
|
+
# if (line[line.length-1, 1] != ".")
|
|
348
348
|
if (!(/\.\z/ =~ line))
|
|
349
349
|
line = line + "." + @origin.to_s + "."
|
|
350
350
|
end
|
|
351
351
|
end
|
|
352
|
-
#
|
|
353
|
-
#
|
|
352
|
+
# Other RRs have several names. These should be parsed by Dnsruby,
|
|
353
|
+
# and the names adjusted there.
|
|
354
354
|
if ([Types::MINFO, Types::PX, Types::RP].include?type_was)
|
|
355
355
|
parsed_rr = Dnsruby::RR.create(line)
|
|
356
356
|
case parsed_rr.type
|
|
@@ -384,11 +384,11 @@ module Dnsruby
|
|
|
384
384
|
return line+"\n"
|
|
385
385
|
end
|
|
386
386
|
|
|
387
|
-
#
|
|
387
|
+
# Get the TTL in seconds from the m, h, d, w format
|
|
388
388
|
def get_ttl(ttl_text_in)
|
|
389
|
-
#
|
|
390
|
-
#
|
|
391
|
-
#
|
|
389
|
+
# If no letter afterwards, then in seconds already
|
|
390
|
+
# Could be e.g. "3d4h12m" - unclear if "4h5w" is legal - best assume it is
|
|
391
|
+
# So, search out each letter in the string, and get the number before it.
|
|
392
392
|
ttl_text = ttl_text_in.downcase
|
|
393
393
|
index = ttl_text.index(/[whdms]/)
|
|
394
394
|
if (!index)
|
|
@@ -421,7 +421,7 @@ module Dnsruby
|
|
|
421
421
|
end
|
|
422
422
|
|
|
423
423
|
def replace_soa_ttl_fields(line)
|
|
424
|
-
#
|
|
424
|
+
# Replace any fields which evaluate to 0
|
|
425
425
|
split = line.split
|
|
426
426
|
4.times {|i|
|
|
427
427
|
x = i + 7
|
|
@@ -431,16 +431,16 @@ module Dnsruby
|
|
|
431
431
|
return split.join(" ") + "\n"
|
|
432
432
|
end
|
|
433
433
|
|
|
434
|
-
#
|
|
435
|
-
#
|
|
436
|
-
#
|
|
437
|
-
#
|
|
438
|
-
#
|
|
434
|
+
# This method is included only for OpenDNSSEC support. It should not be
|
|
435
|
+
# used otherwise.
|
|
436
|
+
# Frig the RR type so that NSEC records appear last in the RRSets.
|
|
437
|
+
# Also make sure that DNSKEYs come first (so we have a key to verify
|
|
438
|
+
# the RRSet with!).
|
|
439
439
|
def prefix_for_rrset_order(type, type_was) # :nodoc: all
|
|
440
|
-
#
|
|
440
|
+
# Now make sure that NSEC(3) RRs go to the back of the list
|
|
441
441
|
if ['NSEC', 'NSEC3'].include?type.string
|
|
442
442
|
if (type_was == Types::RRSIG)
|
|
443
|
-
#
|
|
443
|
+
# Get the RRSIG first
|
|
444
444
|
type_string = "ZZ" + type.string
|
|
445
445
|
else
|
|
446
446
|
type_string = "ZZZ" + type.string
|
|
@@ -448,7 +448,7 @@ module Dnsruby
|
|
|
448
448
|
elsif type == Types::DNSKEY
|
|
449
449
|
type_string = "0" + type.string
|
|
450
450
|
elsif type == Types::NS
|
|
451
|
-
#
|
|
451
|
+
# Make sure that we see the NS records first so we know the delegation status
|
|
452
452
|
type_string = "1" + type.string
|
|
453
453
|
else
|
|
454
454
|
type_string = type.string
|
|
@@ -1,378 +1,378 @@
|
|
|
1
|
-
|
|
2
|
-
#Copyright 2007 Nominet UK
|
|
3
|
-
#
|
|
4
|
-
#Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
#you may not use this file except in compliance with the License.
|
|
6
|
-
#You may obtain a copy of the License at
|
|
7
|
-
#
|
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
#
|
|
10
|
-
#Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
#distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
#See the License for the specific language governing permissions and
|
|
14
|
-
#limitations under the License.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
module Dnsruby
|
|
18
|
-
#
|
|
19
|
-
class ZoneTransfer
|
|
20
|
-
#
|
|
21
|
-
attr_accessor :server
|
|
22
|
-
#
|
|
23
|
-
attr_accessor :transfer_type
|
|
24
|
-
#
|
|
25
|
-
attr_accessor :klass
|
|
26
|
-
#
|
|
27
|
-
attr_accessor :port
|
|
28
|
-
#
|
|
29
|
-
attr_accessor :serial
|
|
30
|
-
#
|
|
31
|
-
attr_reader :tsig
|
|
32
|
-
#
|
|
33
|
-
attr_reader :last_tsigstate
|
|
34
|
-
|
|
35
|
-
#Sets the TSIG to sign the zone transfer with.
|
|
36
|
-
#Pass in either a Dnsruby::RR::TSIG, or a key_name and key (or just a key)
|
|
37
|
-
#Pass in nil to stop tsig signing.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def tsig=(*args)
|
|
42
|
-
@tsig = SingleResolver.get_tsig(args)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def initialize
|
|
47
|
-
@server=Config.new.nameserver[0]
|
|
48
|
-
@transfer_type = Types.AXFR
|
|
49
|
-
@klass=Classes.IN
|
|
50
|
-
@port=53
|
|
51
|
-
@serial=0
|
|
52
|
-
@tsig = nil
|
|
53
|
-
@axfr = nil
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
def transfer(zone)
|
|
83
|
-
servers = @server
|
|
84
|
-
if (servers.class == String)
|
|
85
|
-
servers=[servers]
|
|
86
|
-
end
|
|
87
|
-
xfr = nil
|
|
88
|
-
exception = nil
|
|
89
|
-
servers.each do |server|
|
|
90
|
-
begin
|
|
91
|
-
server=Config.resolve_server(server)
|
|
92
|
-
xfr = do_transfer(zone, server)
|
|
93
|
-
break
|
|
94
|
-
rescue Exception => e
|
|
95
|
-
exception = e
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
if (xfr == nil && exception != nil)
|
|
99
|
-
raise exception
|
|
100
|
-
end
|
|
101
|
-
return xfr
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def do_transfer(zone, server) #:nodoc: all
|
|
105
|
-
@transfer_type = Types.new(@transfer_type)
|
|
106
|
-
@state = :InitialSoa
|
|
107
|
-
socket = TCPSocket.new(server, @port)
|
|
108
|
-
begin
|
|
109
|
-
#
|
|
110
|
-
msg = Message.new(zone, @transfer_type, @klass)
|
|
111
|
-
if @transfer_type == Types.IXFR
|
|
112
|
-
rr = RR.create("#{zone} 0 IN SOA" + '0 0 %u 0 0 0 0' % @serial)
|
|
113
|
-
msg.add_authority(rr)
|
|
114
|
-
end
|
|
115
|
-
send_message(socket, msg)
|
|
116
|
-
|
|
117
|
-
while (@state != :End)
|
|
118
|
-
response = receive_message(socket)
|
|
119
|
-
|
|
120
|
-
if (@state == :InitialSoa)
|
|
121
|
-
rcode = response.rcode
|
|
122
|
-
if (rcode != RCode.NOERROR)
|
|
123
|
-
if (@transfer_type == Types.IXFR &&
|
|
124
|
-
rcode == RCode.NOTIMP)
|
|
125
|
-
#
|
|
126
|
-
Dnsruby.log.debug("IXFR DID NOT WORK (rcode = NOTIMP) - TRYING AXFR!!")
|
|
127
|
-
@state = :InitialSoa
|
|
128
|
-
@transfer_type=Types.AXFR
|
|
129
|
-
#
|
|
130
|
-
msg = Message.new(zone, @transfer_type, @klass)
|
|
131
|
-
send_message(socket, msg)
|
|
132
|
-
next
|
|
133
|
-
end
|
|
134
|
-
raise ResolvError.new(rcode.string);
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
if (response.question[0].qtype != @transfer_type)
|
|
138
|
-
raise ResolvError.new("invalid question section")
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
if (response.header.ancount == 0 && @transfer_type == Types.IXFR)
|
|
142
|
-
Dnsruby.log.debug("IXFR DID NOT WORK (ancount = 0) - TRYING AXFR!!")
|
|
143
|
-
#
|
|
144
|
-
@transfer_type=Types.AXFR
|
|
145
|
-
#
|
|
146
|
-
@state = :InitialSoa
|
|
147
|
-
msg = Message.new(zone, @transfer_type, @klass)
|
|
148
|
-
send_message(socket, msg)
|
|
149
|
-
next
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
response.each_answer { |rr|
|
|
154
|
-
parseRR(rr)
|
|
155
|
-
}
|
|
156
|
-
if (@state == :End &&
|
|
157
|
-
response.tsigstate == :Intermediate)
|
|
158
|
-
raise ResolvError.new("last message must be signed")
|
|
159
|
-
end
|
|
160
|
-
if (@state == :End && @tsig)
|
|
161
|
-
if (response.tsigstate != :Verified)
|
|
162
|
-
@last_tsigstate = :Failed
|
|
163
|
-
raise ResolvError.new("Zone transfer not correctly signed")
|
|
164
|
-
end
|
|
165
|
-
@last_tsigstate = :Verified
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
socket.close
|
|
182
|
-
if (@axfr!=nil)
|
|
183
|
-
return @axfr
|
|
184
|
-
end
|
|
185
|
-
return @ixfr
|
|
186
|
-
rescue Exception => e
|
|
187
|
-
socket.close
|
|
188
|
-
raise e
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
#
|
|
193
|
-
class Delta
|
|
194
|
-
|
|
195
|
-
#
|
|
196
|
-
attr_accessor :start
|
|
197
|
-
|
|
198
|
-
#
|
|
199
|
-
attr_accessor :end
|
|
200
|
-
|
|
201
|
-
#
|
|
202
|
-
attr_accessor :adds
|
|
203
|
-
|
|
204
|
-
#
|
|
205
|
-
attr_accessor :deletes
|
|
206
|
-
|
|
207
|
-
def initialize()
|
|
208
|
-
@adds = []
|
|
209
|
-
@deletes = []
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def to_s
|
|
213
|
-
ret = "Adds : " + @adds.join(",")
|
|
214
|
-
ret +=", Deletes : " + @deletes.join(",")
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
#Compare two serials according to RFC 1982. Return 0 if equal,
|
|
219
|
-
|
|
220
|
-
def compare_serial(s1, s2)
|
|
221
|
-
if s1 == s2
|
|
222
|
-
return 0
|
|
223
|
-
end
|
|
224
|
-
if s1 < s2 and (s2 - s1) < (2**31)
|
|
225
|
-
return 1
|
|
226
|
-
end
|
|
227
|
-
if s1 > s2 and (s1 - s2) > (2**31)
|
|
228
|
-
return 1
|
|
229
|
-
end
|
|
230
|
-
if s1 < s2 and (s2 - s1) > (2**31)
|
|
231
|
-
return -1
|
|
232
|
-
end
|
|
233
|
-
if s1 > s2 and (s1 - s2) < (2**31)
|
|
234
|
-
return -1
|
|
235
|
-
end
|
|
236
|
-
return 0
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
def parseRR(rec) #:nodoc: all
|
|
240
|
-
name = rec.name
|
|
241
|
-
type = rec.type
|
|
242
|
-
delta = Delta.new
|
|
243
|
-
|
|
244
|
-
case @state
|
|
245
|
-
when :InitialSoa
|
|
246
|
-
if (type != Types.SOA)
|
|
247
|
-
raise ResolvError.new("missing initial SOA")
|
|
248
|
-
end
|
|
249
|
-
@initialsoa = rec
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
@end_serial = rec.serial
|
|
253
|
-
#
|
|
254
|
-
if ((@transfer_type == Types.IXFR) && (compare_serial(@end_serial, @serial) >= 0))
|
|
255
|
-
Dnsruby.log.debug("zone up to date")
|
|
256
|
-
raise ZoneSerialError.new("IXFR up to date: expected serial " +
|
|
257
|
-
@serial.to_s + " , got " + rec.serial.to_s);
|
|
258
|
-
@state = :End
|
|
259
|
-
else
|
|
260
|
-
@state = :FirstData
|
|
261
|
-
end
|
|
262
|
-
when :FirstData
|
|
263
|
-
#
|
|
264
|
-
#
|
|
265
|
-
if (@transfer_type == Types.IXFR && type == Types.SOA &&
|
|
266
|
-
rec.serial == @serial)
|
|
267
|
-
Dnsruby.log.debug("IXFR response - using IXFR")
|
|
268
|
-
@rtype = Types.IXFR
|
|
269
|
-
@ixfr = []
|
|
270
|
-
@state = :Ixfr_DelSoa
|
|
271
|
-
else
|
|
272
|
-
Dnsruby.log.debug("AXFR response - using AXFR")
|
|
273
|
-
@rtype = Types.AXFR
|
|
274
|
-
@transfer_type = Types.AXFR
|
|
275
|
-
@axfr = []
|
|
276
|
-
@axfr << @initialsoa
|
|
277
|
-
@state = :Axfr
|
|
278
|
-
end
|
|
279
|
-
parseRR(rec) # Restart...
|
|
280
|
-
return
|
|
281
|
-
|
|
282
|
-
when :Ixfr_DelSoa
|
|
283
|
-
delta = Delta.new
|
|
284
|
-
@ixfr.push(delta)
|
|
285
|
-
delta.start = rec.serial
|
|
286
|
-
delta.deletes << rec
|
|
287
|
-
@state = :Ixfr_Del
|
|
288
|
-
|
|
289
|
-
when :Ixfr_Del
|
|
290
|
-
if (type == Types.SOA)
|
|
291
|
-
@current_serial = rec.serial
|
|
292
|
-
@state = :Ixfr_AddSoa
|
|
293
|
-
parseRR(rec); # Restart...
|
|
294
|
-
return;
|
|
295
|
-
end
|
|
296
|
-
delta = @ixfr[@ixfr.length - 1]
|
|
297
|
-
delta.deletes << rec
|
|
298
|
-
|
|
299
|
-
when :Ixfr_AddSoa
|
|
300
|
-
delta = @ixfr[@ixfr.length - 1]
|
|
301
|
-
delta.end = rec.serial
|
|
302
|
-
delta.adds << rec
|
|
303
|
-
@state = :Ixfr_Add
|
|
304
|
-
|
|
305
|
-
when :Ixfr_Add
|
|
306
|
-
if (type == Types.SOA)
|
|
307
|
-
soa_serial = rec.serial
|
|
308
|
-
if (soa_serial == @end_serial)
|
|
309
|
-
@state = :End
|
|
310
|
-
return
|
|
311
|
-
elsif (soa_serial != @current_serial)
|
|
312
|
-
raise ZoneSerialError.new("IXFR out of sync: expected serial " +
|
|
313
|
-
@current_serial.to_s + " , got " + soa_serial.to_s);
|
|
314
|
-
else
|
|
315
|
-
@state = :Ixfr_DelSoa
|
|
316
|
-
parseRR(rec); # Restart...
|
|
317
|
-
return;
|
|
318
|
-
end
|
|
319
|
-
end
|
|
320
|
-
delta = @ixfr[@ixfr.length - 1]
|
|
321
|
-
delta.adds << rec
|
|
322
|
-
|
|
323
|
-
when :Axfr
|
|
324
|
-
#
|
|
325
|
-
if (type == Types.A && rec.klass() != @klass)
|
|
326
|
-
else
|
|
327
|
-
if (type == Types.SOA)
|
|
328
|
-
@state = :End
|
|
329
|
-
else
|
|
330
|
-
@axfr << rec
|
|
331
|
-
end
|
|
332
|
-
end
|
|
333
|
-
when :End
|
|
334
|
-
raise ResolvError.new("extra data in zone transfer")
|
|
335
|
-
|
|
336
|
-
else
|
|
337
|
-
raise ResolvError.new("invalid state for zone transfer")
|
|
338
|
-
end
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
def send_message(socket, msg) #:nodoc: all
|
|
343
|
-
if (@tsig)
|
|
344
|
-
@tsig.apply(msg)
|
|
345
|
-
@tsig = msg.tsig
|
|
346
|
-
end
|
|
347
|
-
query_packet = msg.encode
|
|
348
|
-
lenmsg = [query_packet.length].pack('n')
|
|
349
|
-
socket.send(lenmsg, 0)
|
|
350
|
-
socket.send(query_packet, 0)
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
def tcp_read(socket, len) #:nodoc: all
|
|
354
|
-
buf=""
|
|
355
|
-
while (buf.length < len) and not socket.eof? do
|
|
356
|
-
buf += socket.read(len-buf.length)
|
|
357
|
-
end
|
|
358
|
-
return buf
|
|
359
|
-
end
|
|
360
|
-
|
|
361
|
-
def receive_message(socket) #:nodoc: all
|
|
362
|
-
buf = tcp_read(socket, 2)
|
|
363
|
-
answersize = buf.unpack('n')[0]
|
|
364
|
-
#
|
|
365
|
-
#
|
|
366
|
-
raise ResolvError.new("Server did not send a valid answer") if answersize.nil?
|
|
367
|
-
buf = tcp_read(socket, answersize)
|
|
368
|
-
msg = Message.decode(buf)
|
|
369
|
-
if (@tsig)
|
|
370
|
-
if !@tsig.verify_envelope(msg, buf)
|
|
371
|
-
Dnsruby.log.error("Bad signature on zone transfer - closing connection")
|
|
372
|
-
raise ResolvError.new("Bad signature on zone transfer")
|
|
373
|
-
end
|
|
374
|
-
end
|
|
375
|
-
return msg
|
|
376
|
-
end
|
|
377
|
-
end
|
|
1
|
+
# --
|
|
2
|
+
# Copyright 2007 Nominet UK
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
# ++
|
|
16
|
+
|
|
17
|
+
module Dnsruby
|
|
18
|
+
# This class performs zone transfers as per RFC1034 (AXFR) and RFC1995 (IXFR).
|
|
19
|
+
class ZoneTransfer
|
|
20
|
+
# The nameserver to use for the zone transfer - defaults to system config
|
|
21
|
+
attr_accessor :server
|
|
22
|
+
# What type of transfer to do (IXFR or AXFR) - defaults to AXFR
|
|
23
|
+
attr_accessor :transfer_type
|
|
24
|
+
# The class - defaults to IN
|
|
25
|
+
attr_accessor :klass
|
|
26
|
+
# The port to connect to - defaults to 53
|
|
27
|
+
attr_accessor :port
|
|
28
|
+
# If using IXFR, this is the SOA serial number to start the incrementals from
|
|
29
|
+
attr_accessor :serial
|
|
30
|
+
# The TSIG record used to sign the transfer
|
|
31
|
+
attr_reader :tsig
|
|
32
|
+
# Returns the tsigstate of the last transfer (nil if no TSIG signed transfer has occurred)
|
|
33
|
+
attr_reader :last_tsigstate
|
|
34
|
+
|
|
35
|
+
# Sets the TSIG to sign the zone transfer with.
|
|
36
|
+
# Pass in either a Dnsruby::RR::TSIG, or a key_name and key (or just a key)
|
|
37
|
+
# Pass in nil to stop tsig signing.
|
|
38
|
+
# * res.tsig=(tsig_rr)
|
|
39
|
+
# * res.tsig=(key_name, key)
|
|
40
|
+
# * res.tsig=nil # Don't sign the transfer
|
|
41
|
+
def tsig=(*args)
|
|
42
|
+
@tsig = SingleResolver.get_tsig(args)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def initialize
|
|
47
|
+
@server=Config.new.nameserver[0]
|
|
48
|
+
@transfer_type = Types.AXFR
|
|
49
|
+
@klass=Classes.IN
|
|
50
|
+
@port=53
|
|
51
|
+
@serial=0
|
|
52
|
+
@tsig = nil
|
|
53
|
+
@axfr = nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Perform a zone transfer (RFC1995)
|
|
57
|
+
# If an IXFR query is unsuccessful, then AXFR is tried (and @transfer_type is set
|
|
58
|
+
# to AXFR)
|
|
59
|
+
# TCP is used as the only transport
|
|
60
|
+
#
|
|
61
|
+
# If AXFR is performed, then the zone will be returned as a set of records :
|
|
62
|
+
#
|
|
63
|
+
# zt = Dnsruby::ZoneTransfer.new
|
|
64
|
+
# zt.transfer_type = Dnsruby::Types.AXFR
|
|
65
|
+
# zt.server = "ns0.validation-test-servers.nominet.org.uk"
|
|
66
|
+
# zone = zt.transfer("validation-test-servers.nominet.org.uk")
|
|
67
|
+
# soa = zone[0]
|
|
68
|
+
# rec1 = zone[1]
|
|
69
|
+
# print zone.to_s
|
|
70
|
+
#
|
|
71
|
+
#
|
|
72
|
+
# If IXFR is performed, then the incrementals will be returned as a set of Deltas.
|
|
73
|
+
# Each Delta contains the start and end SOA serial number, as well as an array of
|
|
74
|
+
# adds and deletes that occurred between the start and end.
|
|
75
|
+
#
|
|
76
|
+
# zt = Dnsruby::ZoneTransfer.new
|
|
77
|
+
# zt.transfer_type = Dnsruby::Types.IXFR
|
|
78
|
+
# zt.server = "ns0.validation-test-servers.nominet.org.uk"
|
|
79
|
+
# zt.serial = 2007090401
|
|
80
|
+
# deltas = zt.transfer("validation-test-servers.nominet.org.uk")
|
|
81
|
+
# assert_equal("Should show up in transfer", deltas[0].adds[1].data)
|
|
82
|
+
def transfer(zone)
|
|
83
|
+
servers = @server
|
|
84
|
+
if (servers.class == String)
|
|
85
|
+
servers=[servers]
|
|
86
|
+
end
|
|
87
|
+
xfr = nil
|
|
88
|
+
exception = nil
|
|
89
|
+
servers.each do |server|
|
|
90
|
+
begin
|
|
91
|
+
server=Config.resolve_server(server)
|
|
92
|
+
xfr = do_transfer(zone, server)
|
|
93
|
+
break
|
|
94
|
+
rescue Exception => e
|
|
95
|
+
exception = e
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
if (xfr == nil && exception != nil)
|
|
99
|
+
raise exception
|
|
100
|
+
end
|
|
101
|
+
return xfr
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def do_transfer(zone, server) #:nodoc: all
|
|
105
|
+
@transfer_type = Types.new(@transfer_type)
|
|
106
|
+
@state = :InitialSoa
|
|
107
|
+
socket = TCPSocket.new(server, @port)
|
|
108
|
+
begin
|
|
109
|
+
# Send an initial query
|
|
110
|
+
msg = Message.new(zone, @transfer_type, @klass)
|
|
111
|
+
if @transfer_type == Types.IXFR
|
|
112
|
+
rr = RR.create("#{zone} 0 IN SOA" + '0 0 %u 0 0 0 0' % @serial)
|
|
113
|
+
msg.add_authority(rr)
|
|
114
|
+
end
|
|
115
|
+
send_message(socket, msg)
|
|
116
|
+
|
|
117
|
+
while (@state != :End)
|
|
118
|
+
response = receive_message(socket)
|
|
119
|
+
|
|
120
|
+
if (@state == :InitialSoa)
|
|
121
|
+
rcode = response.rcode
|
|
122
|
+
if (rcode != RCode.NOERROR)
|
|
123
|
+
if (@transfer_type == Types.IXFR &&
|
|
124
|
+
rcode == RCode.NOTIMP)
|
|
125
|
+
# IXFR didn't work - let's try AXFR
|
|
126
|
+
Dnsruby.log.debug("IXFR DID NOT WORK (rcode = NOTIMP) - TRYING AXFR!!")
|
|
127
|
+
@state = :InitialSoa
|
|
128
|
+
@transfer_type=Types.AXFR
|
|
129
|
+
# Send an initial AXFR query
|
|
130
|
+
msg = Message.new(zone, @transfer_type, @klass)
|
|
131
|
+
send_message(socket, msg)
|
|
132
|
+
next
|
|
133
|
+
end
|
|
134
|
+
raise ResolvError.new(rcode.string);
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
if (response.question[0].qtype != @transfer_type)
|
|
138
|
+
raise ResolvError.new("invalid question section")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if (response.header.ancount == 0 && @transfer_type == Types.IXFR)
|
|
142
|
+
Dnsruby.log.debug("IXFR DID NOT WORK (ancount = 0) - TRYING AXFR!!")
|
|
143
|
+
# IXFR didn't work - let's try AXFR
|
|
144
|
+
@transfer_type=Types.AXFR
|
|
145
|
+
# Send an initial AXFR query
|
|
146
|
+
@state = :InitialSoa
|
|
147
|
+
msg = Message.new(zone, @transfer_type, @klass)
|
|
148
|
+
send_message(socket, msg)
|
|
149
|
+
next
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
response.each_answer { |rr|
|
|
154
|
+
parseRR(rr)
|
|
155
|
+
}
|
|
156
|
+
if (@state == :End &&
|
|
157
|
+
response.tsigstate == :Intermediate)
|
|
158
|
+
raise ResolvError.new("last message must be signed")
|
|
159
|
+
end
|
|
160
|
+
if (@state == :End && @tsig)
|
|
161
|
+
if (response.tsigstate != :Verified)
|
|
162
|
+
@last_tsigstate = :Failed
|
|
163
|
+
raise ResolvError.new("Zone transfer not correctly signed")
|
|
164
|
+
end
|
|
165
|
+
@last_tsigstate = :Verified
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
# This could return with an IXFR response, or an AXFR response.
|
|
169
|
+
# If it fails completely, then try to send an AXFR query.
|
|
170
|
+
# Once the query has been sent, then enter the main response loop.
|
|
171
|
+
# Unless we know we're definitely AXFR, we should be prepared for either IXFR or AXFR
|
|
172
|
+
# AXFR response : The first and the last RR of the response is the SOA record of the zone.
|
|
173
|
+
# The whole zone is returned inbetween.
|
|
174
|
+
# IXFR response : one or more difference sequences is returned. The list of difference
|
|
175
|
+
# sequences is preceded and followed by a copy of the server's current
|
|
176
|
+
# version of the SOA.
|
|
177
|
+
# Each difference sequence represents one update to the zone (one SOA
|
|
178
|
+
# serial change) consisting of deleted RRs and added RRs. The first RR
|
|
179
|
+
# of the deleted RRs is the older SOA RR and the first RR of the added
|
|
180
|
+
# RRs is the newer SOA RR.
|
|
181
|
+
socket.close
|
|
182
|
+
if (@axfr!=nil)
|
|
183
|
+
return @axfr
|
|
184
|
+
end
|
|
185
|
+
return @ixfr
|
|
186
|
+
rescue Exception => e
|
|
187
|
+
socket.close
|
|
188
|
+
raise e
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# All changes between two versions of a zone in an IXFR response.
|
|
193
|
+
class Delta
|
|
194
|
+
|
|
195
|
+
# The starting serial number of this delta.
|
|
196
|
+
attr_accessor :start
|
|
197
|
+
|
|
198
|
+
# The ending serial number of this delta.
|
|
199
|
+
attr_accessor :end
|
|
200
|
+
|
|
201
|
+
# A list of records added between the start and end versions
|
|
202
|
+
attr_accessor :adds
|
|
203
|
+
|
|
204
|
+
# A list of records deleted between the start and end versions
|
|
205
|
+
attr_accessor :deletes
|
|
206
|
+
|
|
207
|
+
def initialize()
|
|
208
|
+
@adds = []
|
|
209
|
+
@deletes = []
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def to_s
|
|
213
|
+
ret = "Adds : " + @adds.join(",")
|
|
214
|
+
ret +=", Deletes : " + @deletes.join(",")
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Compare two serials according to RFC 1982. Return 0 if equal,
|
|
219
|
+
# -1 if s1 is bigger, 1 if s1 is smaller.
|
|
220
|
+
def compare_serial(s1, s2)
|
|
221
|
+
if s1 == s2
|
|
222
|
+
return 0
|
|
223
|
+
end
|
|
224
|
+
if s1 < s2 and (s2 - s1) < (2**31)
|
|
225
|
+
return 1
|
|
226
|
+
end
|
|
227
|
+
if s1 > s2 and (s1 - s2) > (2**31)
|
|
228
|
+
return 1
|
|
229
|
+
end
|
|
230
|
+
if s1 < s2 and (s2 - s1) > (2**31)
|
|
231
|
+
return -1
|
|
232
|
+
end
|
|
233
|
+
if s1 > s2 and (s1 - s2) < (2**31)
|
|
234
|
+
return -1
|
|
235
|
+
end
|
|
236
|
+
return 0
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def parseRR(rec) #:nodoc: all
|
|
240
|
+
name = rec.name
|
|
241
|
+
type = rec.type
|
|
242
|
+
delta = Delta.new
|
|
243
|
+
|
|
244
|
+
case @state
|
|
245
|
+
when :InitialSoa
|
|
246
|
+
if (type != Types.SOA)
|
|
247
|
+
raise ResolvError.new("missing initial SOA")
|
|
248
|
+
end
|
|
249
|
+
@initialsoa = rec
|
|
250
|
+
# Remember the serial number in the initial SOA; we need it
|
|
251
|
+
# to recognize the end of an IXFR.
|
|
252
|
+
@end_serial = rec.serial
|
|
253
|
+
# if ((@transfer_type == Types.IXFR) && (@end_serial <= @serial))
|
|
254
|
+
if ((@transfer_type == Types.IXFR) && (compare_serial(@end_serial, @serial) >= 0))
|
|
255
|
+
Dnsruby.log.debug("zone up to date")
|
|
256
|
+
raise ZoneSerialError.new("IXFR up to date: expected serial " +
|
|
257
|
+
@serial.to_s + " , got " + rec.serial.to_s);
|
|
258
|
+
@state = :End
|
|
259
|
+
else
|
|
260
|
+
@state = :FirstData
|
|
261
|
+
end
|
|
262
|
+
when :FirstData
|
|
263
|
+
# If the transfer begins with 1 SOA, it's an AXFR.
|
|
264
|
+
# If it begins with 2 SOAs, it's an IXFR.
|
|
265
|
+
if (@transfer_type == Types.IXFR && type == Types.SOA &&
|
|
266
|
+
rec.serial == @serial)
|
|
267
|
+
Dnsruby.log.debug("IXFR response - using IXFR")
|
|
268
|
+
@rtype = Types.IXFR
|
|
269
|
+
@ixfr = []
|
|
270
|
+
@state = :Ixfr_DelSoa
|
|
271
|
+
else
|
|
272
|
+
Dnsruby.log.debug("AXFR response - using AXFR")
|
|
273
|
+
@rtype = Types.AXFR
|
|
274
|
+
@transfer_type = Types.AXFR
|
|
275
|
+
@axfr = []
|
|
276
|
+
@axfr << @initialsoa
|
|
277
|
+
@state = :Axfr
|
|
278
|
+
end
|
|
279
|
+
parseRR(rec) # Restart...
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
when :Ixfr_DelSoa
|
|
283
|
+
delta = Delta.new
|
|
284
|
+
@ixfr.push(delta)
|
|
285
|
+
delta.start = rec.serial
|
|
286
|
+
delta.deletes << rec
|
|
287
|
+
@state = :Ixfr_Del
|
|
288
|
+
|
|
289
|
+
when :Ixfr_Del
|
|
290
|
+
if (type == Types.SOA)
|
|
291
|
+
@current_serial = rec.serial
|
|
292
|
+
@state = :Ixfr_AddSoa
|
|
293
|
+
parseRR(rec); # Restart...
|
|
294
|
+
return;
|
|
295
|
+
end
|
|
296
|
+
delta = @ixfr[@ixfr.length - 1]
|
|
297
|
+
delta.deletes << rec
|
|
298
|
+
|
|
299
|
+
when :Ixfr_AddSoa
|
|
300
|
+
delta = @ixfr[@ixfr.length - 1]
|
|
301
|
+
delta.end = rec.serial
|
|
302
|
+
delta.adds << rec
|
|
303
|
+
@state = :Ixfr_Add
|
|
304
|
+
|
|
305
|
+
when :Ixfr_Add
|
|
306
|
+
if (type == Types.SOA)
|
|
307
|
+
soa_serial = rec.serial
|
|
308
|
+
if (soa_serial == @end_serial)
|
|
309
|
+
@state = :End
|
|
310
|
+
return
|
|
311
|
+
elsif (soa_serial != @current_serial)
|
|
312
|
+
raise ZoneSerialError.new("IXFR out of sync: expected serial " +
|
|
313
|
+
@current_serial.to_s + " , got " + soa_serial.to_s);
|
|
314
|
+
else
|
|
315
|
+
@state = :Ixfr_DelSoa
|
|
316
|
+
parseRR(rec); # Restart...
|
|
317
|
+
return;
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
delta = @ixfr[@ixfr.length - 1]
|
|
321
|
+
delta.adds << rec
|
|
322
|
+
|
|
323
|
+
when :Axfr
|
|
324
|
+
# Old BINDs sent cross class A records for non IN classes.
|
|
325
|
+
if (type == Types.A && rec.klass() != @klass)
|
|
326
|
+
else
|
|
327
|
+
if (type == Types.SOA)
|
|
328
|
+
@state = :End
|
|
329
|
+
else
|
|
330
|
+
@axfr << rec
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
when :End
|
|
334
|
+
raise ResolvError.new("extra data in zone transfer")
|
|
335
|
+
|
|
336
|
+
else
|
|
337
|
+
raise ResolvError.new("invalid state for zone transfer")
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def send_message(socket, msg) #:nodoc: all
|
|
343
|
+
if (@tsig)
|
|
344
|
+
@tsig.apply(msg)
|
|
345
|
+
@tsig = msg.tsig
|
|
346
|
+
end
|
|
347
|
+
query_packet = msg.encode
|
|
348
|
+
lenmsg = [query_packet.length].pack('n')
|
|
349
|
+
socket.send(lenmsg, 0)
|
|
350
|
+
socket.send(query_packet, 0)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def tcp_read(socket, len) #:nodoc: all
|
|
354
|
+
buf=""
|
|
355
|
+
while (buf.length < len) and not socket.eof? do
|
|
356
|
+
buf += socket.read(len-buf.length)
|
|
357
|
+
end
|
|
358
|
+
return buf
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def receive_message(socket) #:nodoc: all
|
|
362
|
+
buf = tcp_read(socket, 2)
|
|
363
|
+
answersize = buf.unpack('n')[0]
|
|
364
|
+
# Some servers (e.g. dnscache) apparently hang up on some connections.
|
|
365
|
+
# Thanks to Matt Palmer for the fix.
|
|
366
|
+
raise ResolvError.new("Server did not send a valid answer") if answersize.nil?
|
|
367
|
+
buf = tcp_read(socket, answersize)
|
|
368
|
+
msg = Message.decode(buf)
|
|
369
|
+
if (@tsig)
|
|
370
|
+
if !@tsig.verify_envelope(msg, buf)
|
|
371
|
+
Dnsruby.log.error("Bad signature on zone transfer - closing connection")
|
|
372
|
+
raise ResolvError.new("Bad signature on zone transfer")
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
return msg
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
378
|
end
|