dnsruby 1.39 → 1.40

Sign up to get free protection for your applications and to get access to all the features.
@@ -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