dnsruby 1.39 → 1.40

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.
@@ -32,7 +32,15 @@ module Dnsruby
32
32
  end
33
33
 
34
34
  def from_string(input) #:nodoc: all
35
- @address, @subaddress = input.split(" ")
35
+ address, subaddress = input.split(" ")
36
+ address.sub!(/^\"/, "")
37
+ @address = address.sub(/\"$/, "")
38
+ if (subaddress)
39
+ subaddress.sub!(/^\"/, "")
40
+ @subaddress = subaddress.sub(/\"$/, "")
41
+ else
42
+ @subaddress = nil
43
+ end
36
44
  end
37
45
 
38
46
  def rdata_to_string #:nodoc: all
@@ -0,0 +1,66 @@
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
+ module Dnsruby
17
+ class RR
18
+ #Class for DNS Key Exchange (KX) resource records.
19
+ #RFC 2230
20
+ class KX < RR
21
+ ClassValue = nil #:nodoc: all
22
+ TypeValue= Types::KX #:nodoc: all
23
+
24
+ #The preference for this mail exchange.
25
+ attr_accessor :preference
26
+ #The name of this mail exchange.
27
+ attr_accessor :exchange
28
+
29
+ def from_hash(hash) #:nodoc: all
30
+ @preference = hash[:preference]
31
+ @exchange = Name.create(hash[:exchange])
32
+ end
33
+
34
+ def from_data(data) #:nodoc: all
35
+ @preference, @exchange = data
36
+ end
37
+
38
+ def from_string(input) #:nodoc: all
39
+ if (input.length > 0)
40
+ names = input.split(" ")
41
+ @preference = names[0].to_i
42
+ @exchange = Name.create(names[1])
43
+ end
44
+ end
45
+
46
+ def rdata_to_string #:nodoc: all
47
+ if (@preference!=nil)
48
+ return "#{@preference} #{@exchange}"
49
+ else
50
+ return ""
51
+ end
52
+ end
53
+
54
+ def encode_rdata(msg, canonical=false) #:nodoc: all
55
+ msg.put_pack('n', @preference)
56
+ msg.put_name(@exchange, true)
57
+ end
58
+
59
+ def self.decode_rdata(msg) #:nodoc: all
60
+ preference, = msg.get_unpack('n')
61
+ exchange = msg.get_name
62
+ return self.new([preference, exchange])
63
+ end
64
+ end
65
+ end
66
+ end
@@ -143,9 +143,17 @@ module Dnsruby
143
143
  # What to do for other versions?
144
144
  version = 0;
145
145
 
146
+ horiz_pre = DEFAULT_HORIZ_PRE
147
+ vert_pre = DEFAULT_VERT_PRE
146
148
  latdeg, latmin, latsec, lathem = $1.to_i, $3.to_i, $5.to_i, $6;
147
149
  londeg, lonmin, lonsec, lonhem = $7.to_i, $9.to_i, $11.to_i, $12
148
- alt, size, horiz_pre, vert_pre = $13.to_i, $15.to_i, $17.to_i, $19.to_i
150
+ alt, size = $13.to_i, $15.to_i
151
+ if ($17)
152
+ horiz_pre = $17.to_i
153
+ end
154
+ if ($19)
155
+ vert_pre = $19.to_i
156
+ end
149
157
 
150
158
  latmin = DEFAULT_MIN unless latmin;
151
159
  latsec = DEFAULT_SEC unless latsec;
@@ -156,9 +164,7 @@ module Dnsruby
156
164
  lonhem = lonhem.upcase
157
165
 
158
166
  size = DEFAULT_SIZE unless size;
159
- horiz_pre = DEFAULT_HORIZ_PRE unless horiz_pre;
160
- vert_pre = DEFAULT_VERT_PRE unless vert_pre;
161
-
167
+
162
168
  @version = version;
163
169
  @size = size * 100;
164
170
  @horiz_pre = horiz_pre * 100;
@@ -29,7 +29,9 @@ module Dnsruby
29
29
  end
30
30
 
31
31
  def from_string(input)
32
- @address = input
32
+ address = input
33
+ address.sub!(/^\"/, "")
34
+ @address = address.sub(/\"$/, "")
33
35
  end
34
36
 
35
37
  def rdata_to_string
@@ -150,4 +150,8 @@ require 'Dnsruby/resource/DS'
150
150
  require 'Dnsruby/resource/NSEC3'
151
151
  require 'Dnsruby/resource/NSEC3PARAM'
152
152
  require 'Dnsruby/resource/DLV'
153
- require 'Dnsruby/resource/SSHFP'
153
+ require 'Dnsruby/resource/SSHFP'
154
+ require 'Dnsruby/resource/IPSECKEY'
155
+ require 'Dnsruby/resource/HIP'
156
+ require 'Dnsruby/resource/KX'
157
+ require 'Dnsruby/resource/DHCID'
@@ -534,16 +534,22 @@ module Dnsruby
534
534
  ivars = self.instance_variables
535
535
  s_ivars = []
536
536
  ivars.each {|i| s_ivars << i.to_s} # Ruby 1.9
537
- s_ivars.sort!
538
537
  s_ivars.delete "@ttl" # RFC 2136 section 1.1
539
538
  s_ivars.delete "@rdata"
539
+ if (self.type == Types.DS)
540
+ s_ivars.delete "@digest"
541
+ end
542
+ s_ivars.sort!
540
543
 
541
544
  ivars = other.instance_variables
542
545
  o_ivars = []
543
546
  ivars.each {|i| o_ivars << i.to_s} # Ruby 1.9
544
- o_ivars.sort!
545
547
  o_ivars.delete "@ttl" # RFC 2136 section 1.1
546
548
  o_ivars.delete "@rdata"
549
+ if (other.type == Types.DS)
550
+ o_ivars.delete "@digest"
551
+ end
552
+ o_ivars.sort!
547
553
 
548
554
  return s_ivars == o_ivars &&
549
555
  s_ivars.collect {|name| self.instance_variable_get name} ==
@@ -437,6 +437,7 @@ module Dnsruby
437
437
  end
438
438
 
439
439
  def check_no_wildcard_expansion(msg) # :nodoc:
440
+ # @TODO@ Do this for NSEC3 records!!!
440
441
  proven_no_wildcards = false
441
442
  name = msg.question()[0].qname
442
443
  [msg.authority.rrsets('NSEC'), msg.authority.rrsets('NSEC3')].each {|nsec_rrsets|
@@ -475,6 +476,7 @@ module Dnsruby
475
476
  def check_name_in_nsecs(msg, qtype=nil, expected_qtype = false) # :nodoc:
476
477
  # Check these NSECs to make sure that this name cannot be in the zone
477
478
  # and that no RRSets could match through wildcard expansion
479
+ # @TODO@ Get this right for NSEC3 too!
478
480
  name = msg.question()[0].qname
479
481
  proven_name_in_nsecs = false
480
482
  type_covered_checked = false
@@ -516,6 +518,7 @@ module Dnsruby
516
518
  end
517
519
 
518
520
  def check_name_not_in_wildcard_nsecs(msg) # :nodoc:
521
+ # @TODO@ Do this for NSEC3 records too!
519
522
  name = msg.question()[0].qname
520
523
  qtype = msg.question()[0].qtype
521
524
  done= false
@@ -0,0 +1,381 @@
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
+ # 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
+ # This class provides the facility to load a zone file.
18
+ # It can either process one line at a time, or return an entire zone as a list of
19
+ # records.
20
+ module Dnsruby
21
+ class ZoneReader
22
+ class ParseException < Exception
23
+
24
+ end
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
+ def initialize(origin, soa_minimum = nil, soa_ttl = nil)
28
+ @origin = origin
29
+
30
+ if (!Name.create(@origin).absolute?)
31
+ @origin = @origin + "."
32
+ end
33
+ @soa_ttl = soa_ttl
34
+ if (soa_minimum && !@last_explicit_ttl)
35
+ @last_explicit_ttl = soa_minimum
36
+ else
37
+ @last_explicit_ttl = 0
38
+ end
39
+ @last_explicit_class = Classes.new("IN")
40
+ @last_name = nil
41
+ @continued_line = nil
42
+ @in_quoted_section = false
43
+ end
44
+
45
+ # Takes a filename string and attempts to load a zone. Returns a list
46
+ # of RRs if successful, nil otherwise.
47
+ def process_file(file)
48
+ line_num = 0
49
+ zone = nil
50
+ IO.foreach(file) { |line|
51
+ begin
52
+ ret = process_line(line)
53
+ if (ret)
54
+ rr = RR.create(ret)
55
+ if (!zone)
56
+ zone = []
57
+ end
58
+ zone.push(rr)
59
+ end
60
+ rescue Exception => e
61
+ raise ParseException.new("Error reading line #{line_num} of #{file} : [#{line}]")
62
+ end
63
+ }
64
+ return zone
65
+ end
66
+
67
+ # Process the next line of the file
68
+ # Returns a string representing the normalised line.
69
+ def process_line(line, do_prefix_hack = false)
70
+ return nil if (line.index(';') == 0)
71
+ return nil if (line.strip.length == 0)
72
+ return nil if (!line || (line.length == 0))
73
+ @in_quoted_section = false if !@continued_line
74
+
75
+ line = strip_comments(line)
76
+
77
+ if (line.index("$ORIGIN") == 0)
78
+ @origin = line.split()[1].strip # $ORIGIN <domain-name> [<comment>]
79
+ # print "Setting $ORIGIN to #{@origin}\n"
80
+ return nil
81
+ end
82
+ if (line.index("$TTL") == 0)
83
+ @last_explicit_ttl = get_ttl(line.split()[1].strip) # $TTL <ttl>
84
+ # print "Setting $TTL to #{ttl}\n"
85
+ return nil
86
+ end
87
+ if (@continued_line)
88
+ # Add the next line until we see a ")"
89
+ # REMEMBER TO STRIP OFF COMMENTS!!!
90
+ @continued_line = strip_comments(@continued_line)
91
+ line = @continued_line.strip.chomp + " " + line
92
+ if (line.index(")"))
93
+ # OK
94
+ @continued_line = false
95
+ end
96
+ end
97
+ open_bracket = line.index("(")
98
+ if (open_bracket)
99
+ # Keep going until we see ")"
100
+ index = line.index(")")
101
+ if (index && (index > open_bracket))
102
+ # OK
103
+ @continued_line = false
104
+ else
105
+ @continued_line = line
106
+ end
107
+ end
108
+ return nil if @continued_line
109
+
110
+ line = strip_comments(line) + "\n"
111
+
112
+ # If SOA, then replace "3h" etc. with expanded seconds
113
+ # begin
114
+ return normalise_line(line, do_prefix_hack)
115
+ # rescue Exception => e
116
+ # print "ERROR parsing line #{@line_num} : #{line}\n"
117
+ # return "\n", Types::ANY
118
+ # end
119
+ end
120
+
121
+ def strip_comments(line)
122
+ last_index = 0
123
+ # Are we currently in a quoted section?
124
+ # Does a quoted section begin or end in this line?
125
+ # Are there any semi-colons?
126
+ # Ary any of the semi-colons inside a quoted section?
127
+ while (next_index = line.index(";", last_index + 1))
128
+ # Have there been any quotes since we last looked?
129
+ process_quotes(line[last_index, next_index - last_index])
130
+
131
+ # Now use @in_quoted_section to work out if the ';' terminates the line
132
+ if (!@in_quoted_section)
133
+ return line[0,next_index]
134
+ end
135
+
136
+ last_index = next_index
137
+ end
138
+ # Check out the quote situation to the end of the line
139
+ process_quotes(line[last_index, line.length-1])
140
+
141
+ return line
142
+ end
143
+
144
+ def process_quotes(section)
145
+ # Look through the section of text and set the @in_quoted_section
146
+ # as it should be at the end of the given section
147
+ last_index = 0
148
+ while (next_index = section.index("\"", last_index + 1))
149
+ @in_quoted_section = !@in_quoted_section
150
+ last_index = next_index
151
+ end
152
+ end
153
+
154
+ # Take a line from the input zone file, and return the normalised form
155
+ # do_prefix_hack should always be false
156
+ def normalise_line(line, do_prefix_hack = false)
157
+ # Note that a freestanding "@" is used to denote the current origin - we can simply replace that straight away
158
+ # Remove the ( and )
159
+ # 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.
160
+ if ((line[0,1] == " ") || (line[0,1] == "\t"))
161
+ line = @last_name + " " + line
162
+ end
163
+ line.chomp!
164
+ line.sub!("(", "")
165
+ line.sub!(")", "")
166
+ line.sub!("@ ", "#{@origin} ")
167
+ line.sub!("@\t", "#{@origin} ")
168
+ line.strip!
169
+
170
+
171
+ # o We need to identify the domain name in the record, and then
172
+ split = line.split(' ') # split on whitespace
173
+ name = split[0].strip
174
+ # o add $ORIGIN to it if it is not absolute
175
+ if !(/\.\z/ =~ name)
176
+ new_name = name + "." + @origin
177
+ line.sub!(name, new_name)
178
+ name = new_name
179
+ split = line.split
180
+ end
181
+
182
+ # If the second field is not a number, then we should add the TTL to the line
183
+ # Remember we can get "m" "w" "y" here! So need to check for appropriate regexp...
184
+ found_ttl_regexp = (split[1]=~/^[0-9]+[smhdwSMHDW]/)
185
+ if (found_ttl_regexp == 0)
186
+ # Replace the formatted ttl with an actual number
187
+ ttl = get_ttl(split[1])
188
+ line = name + " #{ttl} "
189
+ @last_explicit_ttl = ttl
190
+ (split.length - 2).times {|i| line += "#{split[i+2]} "}
191
+ line += "\n"
192
+ split = line.split
193
+ elsif (((split[1]).to_i == 0) && (split[1] != "0"))
194
+ # Add the TTL
195
+ if (!@last_explicit_ttl)
196
+ # If this is the SOA record, and no @last_explicit_ttl is defined,
197
+ # then we need to try the SOA TTL element from the config. Otherwise,
198
+ # find the SOA Minimum field, and use that.
199
+ # We should also generate a warning to that effect
200
+ # How do we know if it is an SOA record at this stage? It must be, or
201
+ # else @last_explicit_ttl should be defined
202
+ # We could put a marker in the RR for now - and replace it once we know
203
+ # the actual type. If the type is not SOA then, then we can raise an error
204
+ line = name + " %MISSING_TTL% "
205
+ else
206
+ line = name + " #{@last_explicit_ttl} "
207
+ end
208
+ (split.length - 1).times {|i| line += "#{split[i+1]} "}
209
+ line += "\n"
210
+ split = line.split
211
+ else
212
+ @last_explicit_ttl = split[1].to_i
213
+ end
214
+
215
+ # Now see if the clas is included. If not, then we should default to the last class used.
216
+ begin
217
+ klass = Classes.new(split[2])
218
+ @last_explicit_class = klass
219
+ rescue ArgumentError
220
+ # Wasn't a CLASS
221
+ # So add the last explicit class in
222
+ line = ""
223
+ (2).times {|i| line += "#{split[i]} "}
224
+ line += " #{@last_explicit_class} "
225
+ (split.length - 2).times {|i| line += "#{split[i+2]} "}
226
+ line += "\n"
227
+ split = line.split
228
+ rescue Error => e
229
+ end
230
+
231
+ # Add the type so we can load the zone one RRSet at a time.
232
+ type = Types.new(split[3].strip)
233
+ is_soa = (type == Types::SOA)
234
+ type_was = type
235
+ if (type == Types.RRSIG)
236
+ # 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
237
+ type = Types.new(split[4].strip)
238
+ end
239
+
240
+ type_string=prefix_for_rrset_order(type, type_was)
241
+ @last_name = name
242
+
243
+ if (is_soa)
244
+ if (@soa_ttl)
245
+ # Replace the %MISSING_TTL% text with the SOA TTL from the config
246
+ line.sub!(" %MISSING_TTL% ", " #{@soa_ttl} ")
247
+ else
248
+ # Can we try the @last_explicit_ttl?
249
+ if (@last_explicit_ttl)
250
+ line.sub!(" %MISSING_TTL% ", " #{@last_explicit_ttl} ")
251
+ end
252
+ end
253
+ line = replace_soa_ttl_fields(line)
254
+ if (!@last_explicit_ttl)
255
+ soa_rr = Dnsruby::RR.create(line)
256
+ @last_explicit_ttl = soa_rr.minimum
257
+ end
258
+ end
259
+
260
+ line = line.split.join(' ').strip
261
+ # We need to fix up any non-absolute names in the RR
262
+ # Some RRs have a single name, at the end of the string -
263
+ # to do these, we can just check the last character for "." and add the
264
+ # "." + origin string if necessary
265
+ if ([Types::MX, Types::NS, Types::AFSDB, Types::NAPTR, Types::RT,
266
+ Types::SRV, Types::CNAME, Types::MB, Types::MG, Types::MR,
267
+ Types::PTR].include?type_was)
268
+ if (line[line.length-1, 1] != ".")
269
+ line = line + "." + @origin.to_s
270
+ end
271
+ end
272
+ # Other RRs have several names. These should be parsed by Dnsruby,
273
+ # and the names adjusted there.
274
+ if ([Types::MINFO, Types::PX, Types::RP].include?type_was)
275
+ parsed_rr = Dnsruby::RR.create(line)
276
+ case parsed_rr.type
277
+ when Types::MINFO
278
+ if (!parsed_rr.rmailbx.absolute?)
279
+ parsed_rr.rmailbx = parsed_rr.rmailbx.to_s + "." + @origin.to_s
280
+ end
281
+ if (!parsed_rr.emailbx.absolute?)
282
+ parsed_rr.emailbx = parsed_rr.emailbx.to_s + "." + @origin.to_s
283
+ end
284
+ when Types::PX
285
+ if (!parsed_rr.map822.absolute?)
286
+ parsed_rr.map822 = parsed_rr.map822.to_s + "." + @origin.to_s
287
+ end
288
+ if (!parsed_rr.mapx400.absolute?)
289
+ parsed_rr.mapx400 = parsed_rr.mapx400.to_s + "." + @origin.to_s
290
+ end
291
+ when Types::RP
292
+ if (!parsed_rr.mailbox.absolute?)
293
+ parsed_rr.mailbox = parsed_rr.mailbox.to_s + "." + @origin.to_s
294
+ if (!parsed_rr.txtdomain.absolute?)
295
+ parsed_rr.txtdomain = parsed_rr.txtdomain.to_s + "." + @origin.to_s
296
+ end
297
+ end
298
+ end
299
+ line = parsed_rr.to_s
300
+ end
301
+
302
+ if (do_prefix_hack)
303
+ return line + "\n", type_string, @last_name
304
+ end
305
+ return line+"\n"
306
+ end
307
+
308
+ # Get the TTL in seconds from the m, h, d, w format
309
+ def get_ttl(ttl_text_in)
310
+ # If no letter afterwards, then in seconds already
311
+ # Could be e.g. "3d4h12m" - unclear if "4h5w" is legal - best assume it is
312
+ # So, search out each letter in the string, and get the number before it.
313
+ ttl_text = ttl_text_in.downcase
314
+ index = ttl_text.index(/[whdms]/)
315
+ if (!index)
316
+ return ttl_text.to_i
317
+ end
318
+ last_index = -1
319
+ total = 0
320
+ while (index)
321
+ letter = ttl_text[index]
322
+ number = ttl_text[last_index + 1, index-last_index-1].to_i
323
+ new_number = 0
324
+ case letter
325
+ when 115 then # "s"
326
+ new_number = number
327
+ when 109 then # "m"
328
+ new_number = number * 60
329
+ when 104 then # "h"
330
+ new_number = number * 3600
331
+ when 100 then # "d"
332
+ new_number = number * 86400
333
+ when 119 then # "w"
334
+ new_number = number * 604800
335
+ end
336
+ total += new_number
337
+
338
+ last_index = index
339
+ index = ttl_text.index(/[whdms]/, last_index + 1)
340
+ end
341
+ return total
342
+ end
343
+
344
+ def replace_soa_ttl_fields(line)
345
+ # Replace any fields which evaluate to 0
346
+ split = line.split
347
+ 4.times {|i|
348
+ x = i + 7
349
+ split[x].strip!
350
+ split[x] = get_ttl(split[x]).to_s
351
+ }
352
+ return split.join(" ") + "\n"
353
+ end
354
+
355
+ # This method is included only for OpenDNSSEC support. It should not be
356
+ # used otherwise.
357
+ # Frig the RR type so that NSEC records appear last in the RRSets.
358
+ # Also make sure that DNSKEYs come first (so we have a key to verify
359
+ # the RRSet with!).
360
+ def prefix_for_rrset_order(type, type_was) # :nodoc: all
361
+ # Now make sure that NSEC(3) RRs go to the back of the list
362
+ if ['NSEC', 'NSEC3'].include?type.string
363
+ if (type_was == Types::RRSIG)
364
+ # Get the RRSIG first
365
+ type_string = "ZZ" + type.string
366
+ else
367
+ type_string = "ZZZ" + type.string
368
+ end
369
+ elsif type == Types::DNSKEY
370
+ type_string = "0" + type.string
371
+ elsif type == Types::NS
372
+ # Make sure that we see the NS records first so we know the delegation status
373
+ type_string = "1" + type.string
374
+ else
375
+ type_string = type.string
376
+ end
377
+ return type_string
378
+ end
379
+
380
+ end
381
+ end