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.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +96 -0
  3. data/Rakefile +30 -29
  4. data/demo/axfr.rb +93 -93
  5. data/demo/check_soa.rb +99 -99
  6. data/demo/check_zone.rb +59 -59
  7. data/demo/digdlv.rb +43 -43
  8. data/demo/digroot.rb +34 -34
  9. data/demo/example_recurse.rb +14 -14
  10. data/demo/mresolv.rb +30 -30
  11. data/demo/mx.rb +31 -31
  12. data/demo/rubydig.rb +37 -37
  13. data/demo/to_resolve.txt +3088 -3088
  14. data/demo/trace_dns.rb +46 -46
  15. data/lib/dnsruby.rb +161 -526
  16. data/lib/dnsruby/DNS.rb +305 -0
  17. data/lib/{Dnsruby/Cache.rb → dnsruby/cache.rb} +152 -152
  18. data/lib/{Dnsruby → dnsruby}/code_mapper.rb +48 -52
  19. data/lib/dnsruby/code_mappers.rb +295 -0
  20. data/lib/{Dnsruby/Config.rb → dnsruby/config.rb} +454 -454
  21. data/lib/{Dnsruby → dnsruby}/dnssec.rb +91 -91
  22. data/lib/{Dnsruby/Hosts.rb → dnsruby/hosts.rb} +125 -125
  23. data/lib/{Dnsruby → dnsruby}/ipv4.rb +26 -26
  24. data/lib/{Dnsruby → dnsruby}/ipv6.rb +42 -42
  25. data/lib/{Dnsruby → dnsruby}/key_cache.rb +29 -29
  26. data/lib/dnsruby/message/decoder.rb +164 -0
  27. data/lib/dnsruby/message/encoder.rb +75 -0
  28. data/lib/dnsruby/message/header.rb +249 -0
  29. data/lib/dnsruby/message/message.rb +629 -0
  30. data/lib/dnsruby/message/question.rb +86 -0
  31. data/lib/dnsruby/message/section.rb +96 -0
  32. data/lib/{Dnsruby → dnsruby}/name.rb +141 -141
  33. data/lib/dnsruby/packet_sender.rb +661 -0
  34. data/lib/{Dnsruby/Recursor.rb → dnsruby/recursor.rb} +235 -233
  35. data/lib/dnsruby/resolv.rb +113 -0
  36. data/lib/dnsruby/resolver.rb +1192 -0
  37. data/lib/dnsruby/resource/A.rb +56 -0
  38. data/lib/dnsruby/resource/AAAA.rb +54 -0
  39. data/lib/{Dnsruby → dnsruby}/resource/AFSDB.rb +68 -68
  40. data/lib/{Dnsruby → dnsruby}/resource/CERT.rb +105 -105
  41. data/lib/{Dnsruby → dnsruby}/resource/DHCID.rb +54 -54
  42. data/lib/dnsruby/resource/DLV.rb +27 -0
  43. data/lib/{Dnsruby → dnsruby}/resource/DNSKEY.rb +372 -372
  44. data/lib/{Dnsruby → dnsruby}/resource/DS.rb +255 -255
  45. data/lib/{Dnsruby → dnsruby}/resource/HINFO.rb +71 -71
  46. data/lib/{Dnsruby → dnsruby}/resource/HIP.rb +29 -29
  47. data/lib/{Dnsruby → dnsruby}/resource/IN.rb +30 -30
  48. data/lib/{Dnsruby → dnsruby}/resource/IPSECKEY.rb +31 -31
  49. data/lib/{Dnsruby → dnsruby}/resource/ISDN.rb +62 -62
  50. data/lib/{Dnsruby → dnsruby}/resource/KX.rb +65 -65
  51. data/lib/{Dnsruby → dnsruby}/resource/LOC.rb +263 -263
  52. data/lib/{Dnsruby → dnsruby}/resource/MINFO.rb +69 -69
  53. data/lib/{Dnsruby → dnsruby}/resource/MX.rb +65 -65
  54. data/lib/{Dnsruby → dnsruby}/resource/NAPTR.rb +98 -98
  55. data/lib/{Dnsruby → dnsruby}/resource/NSAP.rb +171 -171
  56. data/lib/dnsruby/resource/NSEC.rb +275 -0
  57. data/lib/dnsruby/resource/NSEC3.rb +332 -0
  58. data/lib/dnsruby/resource/NSEC3PARAM.rb +135 -0
  59. data/lib/dnsruby/resource/OPT.rb +272 -0
  60. data/lib/{Dnsruby → dnsruby}/resource/PX.rb +70 -70
  61. data/lib/{Dnsruby → dnsruby}/resource/RP.rb +75 -75
  62. data/lib/dnsruby/resource/RR.rb +421 -0
  63. data/lib/dnsruby/resource/RRSIG.rb +275 -0
  64. data/lib/dnsruby/resource/RRSet.rb +190 -0
  65. data/lib/{Dnsruby → dnsruby}/resource/RT.rb +67 -67
  66. data/lib/{Dnsruby → dnsruby}/resource/SOA.rb +94 -94
  67. data/lib/dnsruby/resource/SPF.rb +29 -0
  68. data/lib/dnsruby/resource/SRV.rb +112 -0
  69. data/lib/{Dnsruby → dnsruby}/resource/SSHFP.rb +14 -14
  70. data/lib/dnsruby/resource/TKEY.rb +163 -0
  71. data/lib/dnsruby/resource/TSIG.rb +593 -0
  72. data/lib/{Dnsruby → dnsruby}/resource/TXT.rb +191 -191
  73. data/lib/dnsruby/resource/X25.rb +55 -0
  74. data/lib/{Dnsruby → dnsruby}/resource/domain_name.rb +25 -25
  75. data/lib/{Dnsruby → dnsruby}/resource/generic.rb +80 -80
  76. data/lib/dnsruby/resource/resource.rb +25 -0
  77. data/lib/{Dnsruby → dnsruby}/select_thread.rb +148 -148
  78. data/lib/{Dnsruby/SingleResolver.rb → dnsruby/single_resolver.rb} +60 -60
  79. data/lib/{Dnsruby → dnsruby}/single_verifier.rb +344 -344
  80. data/lib/dnsruby/the_log.rb +44 -0
  81. data/lib/dnsruby/update.rb +278 -0
  82. data/lib/dnsruby/validator_thread.rb +124 -0
  83. data/lib/dnsruby/version.rb +3 -0
  84. data/lib/{Dnsruby → dnsruby}/zone_reader.rb +93 -93
  85. data/lib/{Dnsruby → dnsruby}/zone_transfer.rb +377 -377
  86. data/test/spec_helper.rb +16 -0
  87. data/test/tc_axfr.rb +31 -34
  88. data/test/tc_cache.rb +32 -32
  89. data/test/tc_dlv.rb +28 -28
  90. data/test/tc_dns.rb +73 -76
  91. data/test/tc_dnskey.rb +31 -32
  92. data/test/tc_dnsruby.rb +50 -44
  93. data/test/tc_ds.rb +36 -36
  94. data/test/tc_escapedchars.rb +252 -255
  95. data/test/tc_hash.rb +17 -21
  96. data/test/tc_header.rb +48 -57
  97. data/test/tc_hip.rb +19 -22
  98. data/test/tc_ipseckey.rb +18 -21
  99. data/test/tc_keith.rb +300 -0
  100. data/test/tc_message.rb +87 -0
  101. data/test/tc_misc.rb +83 -87
  102. data/test/tc_name.rb +81 -84
  103. data/test/tc_naptr.rb +18 -21
  104. data/test/tc_nsec.rb +55 -55
  105. data/test/tc_nsec3.rb +23 -24
  106. data/test/tc_nsec3param.rb +20 -21
  107. data/test/tc_packet.rb +90 -93
  108. data/test/tc_packet_unique_push.rb +48 -51
  109. data/test/tc_question.rb +30 -33
  110. data/test/tc_queue.rb +16 -17
  111. data/test/tc_recur.rb +16 -17
  112. data/test/tc_res_config.rb +38 -41
  113. data/test/tc_res_env.rb +29 -32
  114. data/test/tc_res_file.rb +26 -29
  115. data/test/tc_res_opt.rb +62 -65
  116. data/test/tc_resolver.rb +287 -242
  117. data/test/tc_rr-opt.rb +70 -63
  118. data/test/tc_rr-txt.rb +68 -71
  119. data/test/tc_rr-unknown.rb +45 -48
  120. data/test/tc_rr.rb +76 -70
  121. data/test/tc_rrset.rb +21 -22
  122. data/test/tc_rrsig.rb +19 -20
  123. data/test/tc_single_resolver.rb +294 -297
  124. data/test/tc_soak.rb +199 -202
  125. data/test/tc_soak_base.rb +29 -34
  126. data/test/tc_sshfp.rb +20 -23
  127. data/test/tc_tcp.rb +32 -35
  128. data/test/tc_tkey.rb +41 -44
  129. data/test/tc_tsig.rb +81 -84
  130. data/test/tc_update.rb +108 -111
  131. data/test/tc_validator.rb +29 -29
  132. data/test/tc_verifier.rb +81 -82
  133. data/test/ts_dnsruby.rb +16 -15
  134. data/test/ts_offline.rb +62 -63
  135. data/test/ts_online.rb +115 -115
  136. metadata +155 -90
  137. data/README +0 -59
  138. data/lib/Dnsruby/DNS.rb +0 -305
  139. data/lib/Dnsruby/PacketSender.rb +0 -656
  140. data/lib/Dnsruby/Resolver.rb +0 -1189
  141. data/lib/Dnsruby/TheLog.rb +0 -44
  142. data/lib/Dnsruby/message.rb +0 -1230
  143. data/lib/Dnsruby/resource/A.rb +0 -56
  144. data/lib/Dnsruby/resource/AAAA.rb +0 -54
  145. data/lib/Dnsruby/resource/DLV.rb +0 -27
  146. data/lib/Dnsruby/resource/NSEC.rb +0 -298
  147. data/lib/Dnsruby/resource/NSEC3.rb +0 -340
  148. data/lib/Dnsruby/resource/NSEC3PARAM.rb +0 -135
  149. data/lib/Dnsruby/resource/OPT.rb +0 -213
  150. data/lib/Dnsruby/resource/RRSIG.rb +0 -275
  151. data/lib/Dnsruby/resource/SPF.rb +0 -29
  152. data/lib/Dnsruby/resource/SRV.rb +0 -112
  153. data/lib/Dnsruby/resource/TKEY.rb +0 -163
  154. data/lib/Dnsruby/resource/TSIG.rb +0 -593
  155. data/lib/Dnsruby/resource/X25.rb +0 -55
  156. data/lib/Dnsruby/resource/resource.rb +0 -678
  157. data/lib/Dnsruby/update.rb +0 -278
  158. data/lib/Dnsruby/validator_thread.rb +0 -124
@@ -0,0 +1,421 @@
1
+ # Superclass for all Dnsruby resource records.
2
+ #
3
+ # Represents a DNS RR (resource record) [RFC1035, section 3.2]
4
+ #
5
+ # Use Dnsruby::RR::create(...) to create a new RR record.
6
+ #
7
+ # mx = Dnsruby::RR.create("example.com. 7200 MX 10 mailhost.example.com.")
8
+ #
9
+ # rr = Dnsruby::RR.create({:name => "example.com", :type => "MX", :ttl => 7200,
10
+ # :preference => 10, :exchange => "mailhost.example.com"})
11
+ #
12
+ # s = rr.to_s # Get a String representation of the RR (in zone file format)
13
+ # rr_again = Dnsruby::RR.create(s)
14
+ #
15
+
16
+ require 'dnsruby/code_mappers'
17
+
18
+ module Dnsruby
19
+ class RR
20
+
21
+ include Comparable
22
+
23
+ def <=>(other)
24
+ # return 1 if ((!other) || !(other.name) || !(other.type))
25
+ # return -1 if (!@name)
26
+ if @name.canonical == other.name.canonical
27
+ @type.code != other.type.code ? (@type.code <=> other.type.code) : (@rdata <=> other.rdata)
28
+ else
29
+ @name <=> other.name
30
+ end
31
+ end
32
+
33
+ # A regular expression which catches any valid resource record.
34
+ @@RR_REGEX = Regexp.new("^\\s*(\\S+)\\s*(\\d+)?\\s*(#{Classes.regexp +
35
+ "|CLASS\\d+"})?\\s*(#{Types.regexp + '|TYPE\\d+'})?\\s*([\\s\\S]*)\$") #:nodoc: all
36
+
37
+ @@implemented_rr_map = nil
38
+
39
+ # The Resource's domain name
40
+ attr_reader :name
41
+
42
+ # The Resource type
43
+ attr_reader :type
44
+
45
+ # The Resource class
46
+ attr_reader :klass
47
+
48
+ # The Resource Time-To-Live
49
+ attr_accessor :ttl
50
+
51
+ # The Resource data section
52
+ attr_accessor :rdata
53
+
54
+ def rdlength
55
+ rdata.length
56
+ end
57
+
58
+ def name=(new_name)
59
+ @name = new_name.kind_of?(Name) ? new_name : Name.create(new_name)
60
+ end
61
+
62
+ def type=(type)
63
+ @type = Types.new(type)
64
+ end
65
+ alias :rr_type :type
66
+
67
+ def klass=(klass)
68
+ if @type != Types::OPT
69
+ @klass = Classes.new(klass)
70
+ else
71
+ @klass = klass.is_a?(Classes) ? klass : Classes.new("CLASS#{klass}")
72
+ end
73
+ end
74
+
75
+ def clone
76
+ encoded = MessageEncoder.new { |encoder| encoder.put_rr(self, true) }.to_s
77
+ MessageDecoder.new(encoded).get_rr
78
+ end
79
+
80
+
81
+ # Determines if two Records could be part of the same RRset.
82
+ # This compares the name, type, and class of the Records; the ttl and
83
+ # rdata are not compared.
84
+ def sameRRset(rec)
85
+ if @klass != rec.klass || @name.downcase != rec.name.downcase
86
+ return false
87
+ elsif (rec.type == Types.RRSIG) && (@type == Types.RRSIG)
88
+ return rec.type_covered == self.type_covered
89
+ end
90
+ [rec, self].each do |rr|
91
+ if rr.type == Types::RRSIG
92
+ return (@type == rr.type_covered) || (rec.type == rr.type_covered)
93
+ end
94
+ end
95
+ @type == rec.type
96
+ end
97
+
98
+ def init_defaults
99
+ # Default to do nothing
100
+ end
101
+
102
+ private
103
+ def initialize(*args) #:nodoc: all
104
+ init_defaults
105
+ if args.length > 0
106
+ if args[0].class == Hash
107
+ from_hash(args[0])
108
+ return
109
+ else
110
+ @rdata = args[0]
111
+ # print "Loading RR from #{args[0]}, class : #{args[0].class}\n"
112
+ if args[0].class == String
113
+ from_string(args[0])
114
+ return
115
+ else
116
+ from_data(args[0])
117
+ return
118
+ end
119
+ end
120
+ end
121
+ # raise ArgumentError.new("Don't call new! Use Dnsruby::RR::create() instead")
122
+ end
123
+ public
124
+
125
+ def from_hash(hash) #:nodoc: all
126
+ hash.keys.each do |param|
127
+ send("#{param}=", hash[param])
128
+ end
129
+ end
130
+
131
+ # Create a new RR from the hash. The name is required; all other fields are optional.
132
+ # Type defaults to ANY and the Class defaults to IN. The TTL defaults to 0.
133
+ #
134
+ # If the type is specified, then it is necessary to provide ALL of the resource record fields which
135
+ # are specific to that record; i.e. for
136
+ # an MX record, you would need to specify the exchange and the preference
137
+ #
138
+ # require 'Dnsruby'
139
+ # rr = Dnsruby::RR.new_from_hash({:name => "example.com"})
140
+ # rr = Dnsruby::RR.new_from_hash({:name => "example.com", :type => Types.MX, :ttl => 10, :preference => 5, :exchange => "mx1.example.com"})
141
+ def RR.new_from_hash(inhash)
142
+ hash = inhash.clone
143
+ type = hash[:type] || Types::ANY
144
+ klass = hash[:klass] || Classes::IN
145
+ ttl = hash[:ttl] || 0
146
+ record_class = get_class(type, klass)
147
+ record = record_class.new
148
+ record.name = hash[:name]
149
+ unless record.name.kind_of?(Name)
150
+ record.name = Name.create(record.name)
151
+ end
152
+ record.ttl = ttl
153
+ record.type = type
154
+ record.klass = klass
155
+ hash.delete(:name)
156
+ hash.delete(:type)
157
+ hash.delete(:ttl)
158
+ hash.delete(:klass)
159
+ record.from_hash(hash)
160
+ record
161
+ end
162
+
163
+ # Returns a Dnsruby::RR object of the appropriate type and
164
+ # initialized from the string passed by the user. The format of the
165
+ # string is that used in zone files, and is compatible with the string
166
+ # returned by Net::DNS::RR.inspect
167
+ #
168
+ # The name and RR type are required; all other information is optional.
169
+ # If omitted, the TTL defaults to 0 and the RR class defaults to IN.
170
+ #
171
+ # All names must be fully qualified. The trailing dot (.) is optional.
172
+ #
173
+ #
174
+ # a = Dnsruby::RR.new_from_string("foo.example.com. 86400 A 10.1.2.3")
175
+ # mx = Dnsruby::RR.new_from_string("example.com. 7200 MX 10 mailhost.example.com.")
176
+ # cname = Dnsruby::RR.new_from_string("www.example.com 300 IN CNAME www1.example.com")
177
+ # txt = Dnsruby::RR.new_from_string('baz.example.com 3600 HS TXT "text record"')
178
+ #
179
+ #
180
+ def RR.new_from_string(rrstring)
181
+ # strip out comments
182
+ # Test for non escaped ";" by means of the look-behind assertion
183
+ # (the backslash is escaped)
184
+ rrstring = rrstring.gsub(/(\?<!\\);.*/o, '')
185
+
186
+ matches = (/#{@@RR_REGEX}/xo).match(rrstring)
187
+ unless matches
188
+ raise "#{rrstring} did not match RR pattern. Please report this to the author!"
189
+ end
190
+
191
+ name = matches[1]
192
+ ttl = matches[2].to_i || 0
193
+ rrclass = matches[3] || ''
194
+ rrtype = matches[4] || ''
195
+ rdata = matches[5] || ''
196
+
197
+ rdata.gsub!(/\s+$/o, '') if rdata
198
+
199
+ # RFC3597 tweaks
200
+ # This converts to known class and type if specified as TYPE###
201
+ if rrtype =~/^TYPE\d+/o
202
+ rrtype = Dnsruby::Types.typesbyval(Dnsruby::Types::typesbyname(rrtype))
203
+ end
204
+ if rrclass =~/^CLASS\d+/o
205
+ rrclass = Dnsruby::Classes.classesbyval(Dnsruby::Classes::classesbyname(rrclass))
206
+ end
207
+
208
+ if rrtype == '' && rrclass == 'ANY'
209
+ rrtype = 'ANY'
210
+ rrclass = 'IN'
211
+ elsif rrclass == ''
212
+ rrclass = 'IN'
213
+ end
214
+
215
+ if rrtype == ''
216
+ rrtype = 'ANY'
217
+ end
218
+
219
+ unless %w(NAPTR TXT).include?(rrtype)
220
+ if rdata
221
+ rdata.gsub!('(', '')
222
+ rdata.gsub!(')', '')
223
+ end
224
+ end
225
+
226
+ test_length = ->(hexdump, rdlength) do
227
+ if hexdump.length != rdlength * 2
228
+ raise "#{rdata} is inconsistent; length should be #{rdlength * 2} but is #{hexdump.length}."
229
+ end
230
+ end
231
+
232
+ pack_rdata = ->(regex) do
233
+ rdata =~ regex
234
+ matches = regex.match(rdata)
235
+ rdlength = matches[1].to_i
236
+ hexdump = matches[2].gsub(/\s*/, '')
237
+
238
+ test_length.(hexdump, rdlength)
239
+ packed_rdata = [hexdump].pack('H*')
240
+
241
+ [packed_rdata, rdlength]
242
+ end
243
+
244
+ if implemented_rrs.include?(rrtype) && rdata !~/^\s*\\#/o
245
+ return _get_subclass(name, rrtype, rrclass, ttl, rdata)
246
+ elsif implemented_rrs.include?(rrtype) # A known RR type starting with \#
247
+ packed_rdata, rdlength = pack_rdata.(/\\#\s+(\d+)\s+(.*)$/o)
248
+ return new_from_data(name, rrtype, rrclass, ttl, rdlength, packed_rdata, 0) # rdata.length() - rdlength);
249
+ elsif rdata =~ /\s*\\#\s+\d+\s+/o
250
+ regex = /\\#\s+(\d+)\s+(.*)$/o
251
+ # We are now dealing with the truly unknown.
252
+ raise 'Expected RFC3597 representation of RDATA' unless rdata =~ regex
253
+ packed_rdata, rdlength = pack_rdata.(regex)
254
+ return new_from_data(name, rrtype, rrclass, ttl, rdlength, packed_rdata, 0) # rdata.length() - rdlength);
255
+ else
256
+ # God knows how to handle these...
257
+ return _get_subclass(name, rrtype, rrclass, ttl, '')
258
+ end
259
+ end
260
+
261
+ def RR.new_from_data(*args) #:nodoc: all
262
+ name, rrtype, rrclass, ttl, rdlength, data, offset = args
263
+ rdata = data ? data[offset, rdlength] : []
264
+ decoder = MessageDecoder.new(rdata)
265
+ record = get_class(rrtype, rrclass).decode_rdata(decoder)
266
+ record.name = Name.create(name)
267
+ record.ttl = ttl
268
+ record.type = rrtype
269
+ record.klass = rrclass
270
+ record
271
+ end
272
+
273
+ # Return an array of all the currently implemented RR types
274
+ def RR.implemented_rrs
275
+ @@implemented_rr_map ||= ClassHash.keys.map { |key| Dnsruby::Types.to_string(key[0]) }
276
+ end
277
+
278
+ class << self
279
+ private
280
+ def _get_subclass(name, rrtype, rrclass, ttl, rdata) #:nodoc: all
281
+ return unless (rrtype!=nil)
282
+ record = get_class(rrtype, rrclass).new(rdata)
283
+ record.name = Name.create(name)
284
+ record.ttl = ttl
285
+ record.type = rrtype
286
+ record.klass = rrclass
287
+ return record
288
+ end
289
+ end
290
+
291
+ # Returns a string representation of the RR in zone file format
292
+ def to_s
293
+ s = name ? (name.to_s(true) + "\t") : ''
294
+ s << [ttl, klass, type, rdata_to_string].map(&:to_s).join("\t")
295
+ end
296
+
297
+ # Get a string representation of the data section of the RR (in zone file format)
298
+ def rdata_to_string
299
+ (@rdata && @rdata.length > 0) ? @rdata : 'no rdata'
300
+ end
301
+
302
+ def from_data(data) #:nodoc: all
303
+ # to be implemented by subclasses
304
+ raise NotImplementedError.new
305
+ end
306
+
307
+ def from_string(input) #:nodoc: all
308
+ # to be implemented by subclasses
309
+ # raise NotImplementedError.new
310
+ end
311
+
312
+ def encode_rdata(msg, canonical=false) #:nodoc: all
313
+ # to be implemented by subclasses
314
+ raise EncodeError.new("#{self.class} is RR.")
315
+ end
316
+
317
+ def self.decode_rdata(msg) #:nodoc: all
318
+ # to be implemented by subclasses
319
+ raise DecodeError.new("#{self.class} is RR.")
320
+ end
321
+
322
+ def ==(other)
323
+
324
+ return false unless self.class == other.class
325
+
326
+ ivars_to_compare = ->(object) do
327
+ ivars = object.instance_variables.map { |var| var.to_s }
328
+ ivars.delete '@ttl' # RFC 2136 section 1.1
329
+ ivars.delete '@rdata'
330
+ if self.type == Types.DS
331
+ ivars.delete '@digest'
332
+ end
333
+ ivars.sort
334
+ end
335
+
336
+ get_instance_var_values = ->(object, ivar_names) do
337
+ ivar_names.map { |ivar_name| object.instance_variable_get(ivar_name) }
338
+ end
339
+
340
+ self_ivars = ivars_to_compare.(self)
341
+ other_ivars = ivars_to_compare.(other)
342
+ return false unless self_ivars == other_ivars
343
+
344
+ self_values = get_instance_var_values.(self, self_ivars)
345
+ other_values = get_instance_var_values.(other, other_ivars)
346
+ self_values == other_values
347
+ end
348
+
349
+ def eql?(other) #:nodoc:
350
+ self == other
351
+ end
352
+
353
+ def hash # :nodoc:
354
+ vars = self.instance_variables - ['@ttl']
355
+ vars.inject(0) do |hash_value, var_name|
356
+ hash_value ^ self.instance_variable_get(var_name).hash
357
+ end
358
+ end
359
+
360
+ def self.find_class(type_value, class_value) # :nodoc: all
361
+ if !! (ret = ClassHash[[type_value, class_value]])
362
+ return ret
363
+ elsif !! (val = ClassInsensitiveTypes[type_value])
364
+ klass = Class.new(val)
365
+ klass.const_set(:TypeValue, type_value)
366
+ klass.const_set(:ClassValue, class_value)
367
+ return klass
368
+ else
369
+ return Generic.create(type_value, class_value)
370
+ end
371
+ end
372
+
373
+ # Get an RR of the specified type and class
374
+ def self.get_class(type_value, class_value) #:nodoc: all
375
+ if type_value == Types::OPT
376
+ return Class.new(OPT)
377
+ elsif type_value.class == Class
378
+ type_value = type_value.const_get(:TypeValue)
379
+ return find_class(type_value, Classes.to_code(class_value))
380
+ else
381
+ type_value = (type_value.class == Types) ? type_value.code : Types.new(type_value).code
382
+ class_value = (class_value.class == Classes) ? class_value.code : Classes.new(class_value).code
383
+ return find_class(type_value, class_value)
384
+ end
385
+ end
386
+
387
+
388
+ # Create a new RR from the arguments, which can be either a String or a Hash.
389
+ # See new_from_string and new_from_hash for details
390
+ #
391
+ # a = Dnsruby::RR.create('foo.example.com. 86400 A 10.1.2.3')
392
+ # mx = Dnsruby::RR.create('example.com. 7200 MX 10 mailhost.example.com.')
393
+ # cname = Dnsruby::RR.create('www.example.com 300 IN CNAME www1.example.com')
394
+ # txt = Dnsruby::RR.create('baz.example.com 3600 HS TXT 'text record'')
395
+ #
396
+ # rr = Dnsruby::RR.create({:name => 'example.com'})
397
+ # rr = Dnsruby::RR.create({:name => 'example.com', :type => 'MX', :ttl => 10,
398
+ # :preference => 5, :exchange => 'mx1.example.com'})
399
+ #
400
+ def RR.create(*args)
401
+ case args[0]
402
+ when String
403
+ new_from_string(args[0])
404
+ when Hash
405
+ new_from_hash(args[0])
406
+ else
407
+ new_from_data(args)
408
+ end
409
+ end
410
+
411
+ def self.get_num(bytes)
412
+ ret = 0
413
+ shift = (bytes.length - 1) * 8
414
+ bytes.each_byte do |byte|
415
+ ret += byte.to_i << shift
416
+ shift -= 8
417
+ end
418
+ ret
419
+ end
420
+ end
421
+ end
@@ -0,0 +1,275 @@
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
+ # (RFC4034, section 3)
19
+ # DNSSEC uses public key cryptography to sign and authenticate DNS
20
+ # resource record sets (RRsets). Digital signatures are stored in
21
+ # RRSIG resource records and are used in the DNSSEC authentication
22
+ # process described in [RFC4035]. A validator can use these RRSIG RRs
23
+ # to authenticate RRsets from the zone. The RRSIG RR MUST only be used
24
+ # to carry verification material (digital signatures) used to secure
25
+ # DNS operations.
26
+ #
27
+ # An RRSIG record contains the signature for an RRset with a particular
28
+ # name, class, and type. The RRSIG RR specifies a validity interval
29
+ # for the signature and uses the Algorithm, the Signer's Name, and the
30
+ # Key Tag to identify the DNSKEY RR containing the public key that a
31
+ # validator can use to verify the signature.
32
+ class RRSIG < RR
33
+ ClassValue = nil #:nodoc: all
34
+ TypeValue = Types::RRSIG #:nodoc: all
35
+
36
+ # 3.1. RRSIG RDATA Wire Format
37
+ #
38
+ # The RDATA for an RRSIG RR consists of a 2 octet Type Covered field, a
39
+ # 1 octet Algorithm field, a 1 octet Labels field, a 4 octet Original
40
+ # TTL field, a 4 octet Signature Expiration field, a 4 octet Signature
41
+ # Inception field, a 2 octet Key tag, the Signer's Name field, and the
42
+ # Signature field.
43
+ #
44
+ # 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
45
+ # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
46
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
47
+ # | Type Covered | Algorithm | Labels |
48
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
49
+ # | Original TTL |
50
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
51
+ # | Signature Expiration |
52
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
53
+ # | Signature Inception |
54
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
55
+ # | Key Tag | /
56
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Signer's Name /
57
+ # / /
58
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
59
+ # / /
60
+ # / Signature /
61
+ # / /
62
+ # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
63
+
64
+ # The type covered by this RRSIG
65
+ attr_reader :type_covered
66
+ # The algorithm used for this RRSIG
67
+ # See Dnsruby::Algorithms for permitted values
68
+ attr_reader :algorithm
69
+ # The number of labels in the original RRSIG RR owner name
70
+ # Can be used to determine if name was synthesised from a wildcard.
71
+ attr_accessor :labels
72
+ # The TTL of the covered RRSet as it appears in the authoritative zone
73
+ attr_accessor :original_ttl
74
+ # The signature expiration
75
+ attr_accessor :expiration
76
+ # The signature inception
77
+ attr_accessor :inception
78
+ # The key tag value of the DNSKEY RR that validates this signature
79
+ attr_accessor :key_tag
80
+ # identifies the owner name of the DNSKEY RR that a validator is
81
+ # supposed to use to validate this signature
82
+ attr_reader :signers_name
83
+
84
+ # contains the cryptographic signature that covers
85
+ # the RRSIG RDATA (excluding the Signature field) and the RRset
86
+ # specified by the RRSIG owner name, RRSIG class, and RRSIG Type
87
+ # Covered field
88
+ attr_accessor :signature
89
+
90
+ def init_defaults
91
+ @algorithm=Algorithms.RSASHA1
92
+ @type_covered = Types::A
93
+ @original_ttl = 3600
94
+ @inception = Time.now.to_i
95
+ @expiration = Time.now.to_i
96
+ @key_tag = 0
97
+ @labels = 0
98
+ self.signers_name="."
99
+ @signature = "\0"
100
+ end
101
+
102
+ def algorithm=(a)
103
+ if (a.instance_of?String)
104
+ if (a.to_i > 0)
105
+ a = a.to_i
106
+ end
107
+ end
108
+ begin
109
+ alg = Algorithms.new(a)
110
+ @algorithm = alg
111
+ rescue ArgumentError => e
112
+ raise DecodeError.new(e)
113
+ end
114
+ end
115
+
116
+ def type_covered=(t)
117
+ begin
118
+ type = Types.new(t)
119
+ @type_covered = type
120
+ rescue ArgumentError => e
121
+ raise DecodeError.new(e)
122
+ end
123
+ end
124
+
125
+ def signers_name=(s)
126
+ begin
127
+ name = Name.create(s)
128
+ @signers_name = name
129
+ rescue ArgumentError => e
130
+ raise DecodeError.new(e)
131
+ end
132
+ end
133
+
134
+
135
+ def from_data(data) #:nodoc: all
136
+ type_covered, algorithm, @labels, @original_ttl, expiration, inception,
137
+ @key_tag, signers_name, @signature = data
138
+ @expiration = expiration
139
+ @inception = inception
140
+ self.type_covered=(type_covered)
141
+ self.signers_name=(signers_name)
142
+ self.algorithm=(algorithm)
143
+ end
144
+
145
+ def from_string(input)
146
+ if (input.length > 0)
147
+ data = input.split(" ")
148
+ self.type_covered=(data[0])
149
+ self.algorithm=(data[1])
150
+ self.labels=data[2].to_i
151
+ self.original_ttl=data[3].to_i
152
+ self.expiration=get_time(data[4])
153
+ # Brackets may also be present
154
+ index = 5
155
+ end_index = data.length - 1
156
+ if (data[index]=="(")
157
+ index = 6
158
+ end_index = data.length - 2
159
+ end
160
+ self.inception=get_time(data[index])
161
+ self.key_tag=data[index+1].to_i
162
+ self.signers_name=(data[index+2])
163
+ # signature can include whitespace - include all text
164
+ # until we come to " )" at the end, and then gsub
165
+ # the white space out
166
+ buf=""
167
+ (index+3..end_index).each {|i|
168
+ if (comment_index = data[i].index(";"))
169
+ buf += data[i].slice(0, comment_index)
170
+ # @TODO@ We lose the comments here - we should really keep them for when we write back to string format?
171
+ break
172
+ else
173
+ buf += data[i]
174
+ end
175
+ }
176
+ buf.gsub!(/\n/, "")
177
+ buf.gsub!(/ /, "")
178
+ # self.signature=Base64.decode64(buf)
179
+ self.signature=buf.unpack("m*")[0]
180
+ end
181
+ end
182
+
183
+ def RRSIG.get_time(input)
184
+ if (input.kind_of?Fixnum)
185
+ return input
186
+ end
187
+ # RFC 4034, section 3.2
188
+ # The Signature Expiration Time and Inception Time field values MUST be
189
+ # represented either as an unsigned decimal integer indicating seconds
190
+ # since 1 January 1970 00:00:00 UTC, or in the form YYYYMMDDHHmmSS in
191
+ # UTC, where:
192
+ #
193
+ # YYYY is the year (0001-9999, but see Section 3.1.5);
194
+ # MM is the month number (01-12);
195
+ # DD is the day of the month (01-31);
196
+ # HH is the hour, in 24 hour notation (00-23);
197
+ # mm is the minute (00-59); and
198
+ # SS is the second (00-59).
199
+ #
200
+ # Note that it is always possible to distinguish between these two
201
+ # formats because the YYYYMMDDHHmmSS format will always be exactly 14
202
+ # digits, while the decimal representation of a 32-bit unsigned integer
203
+ # can never be longer than 10 digits.
204
+ if (input.length == 10)
205
+ return input.to_i
206
+ elsif (input.length == 14)
207
+ year = input[0,4]
208
+ mon=input[4,2]
209
+ day=input[6,2]
210
+ hour=input[8,2]
211
+ min=input[10,2]
212
+ sec=input[12,2]
213
+ # @TODO@ REPLACE THIS BY LOCAL CODE - Time.gm DOG SLOW!
214
+ return Time.gm(year, mon, day, hour, min, sec).to_i
215
+ else
216
+ raise DecodeError.new("RRSIG : Illegal time value #{input} - see RFC 4034 section 3.2")
217
+ end
218
+ end
219
+
220
+ def get_time(input)
221
+ return RRSIG.get_time(input)
222
+ end
223
+
224
+ def format_time(time)
225
+ return Time.at(time).gmtime.strftime("%Y%m%d%H%M%S")
226
+ end
227
+
228
+ def rdata_to_string #:nodoc: all
229
+ if (@type_covered!=nil)
230
+ # signature = Base64.encode64(@signature) # .gsub(/\n/, "")
231
+ signature = [@signature].pack("m*").gsub(/\n/, "")
232
+ # @TODO@ Display the expiration and inception as
233
+ return "#{@type_covered.string} #{@algorithm.string} #{@labels} #{@original_ttl} " +
234
+ "#{format_time(@expiration)} ( #{format_time(@inception)} " +
235
+ "#{@key_tag} #{@signers_name.to_s(true)} #{signature} )"
236
+ else
237
+ return ""
238
+ end
239
+ end
240
+
241
+ def encode_rdata(msg, canonical=false) #:nodoc: all
242
+ # 2 octets, then 2 sets of 1 octet
243
+ msg.put_pack('ncc', @type_covered.to_i, @algorithm.to_i, @labels)
244
+ msg.put_pack("NNN", @original_ttl, @expiration, @inception)
245
+ msg.put_pack("n", @key_tag)
246
+ msg.put_name(@signers_name, canonical, false)
247
+ msg.put_bytes(@signature)
248
+ end
249
+
250
+ def self.decode_rdata(msg) #:nodoc: all
251
+ type_covered, algorithm, labels = msg.get_unpack('ncc')
252
+ original_ttl, expiration, inception = msg.get_unpack('NNN')
253
+ key_tag, = msg.get_unpack('n')
254
+ signers_name = msg.get_name
255
+ signature = msg.get_bytes
256
+ return self.new(
257
+ [type_covered, algorithm, labels, original_ttl, expiration,
258
+ inception, key_tag, signers_name, signature])
259
+ end
260
+
261
+ def sig_data
262
+ # RRSIG_RDATA is the wire format of the RRSIG RDATA fields
263
+ # with the Signer's Name field in canonical form and
264
+ # the Signature field excluded;
265
+ data = MessageEncoder.new { |msg|
266
+ msg.put_pack('ncc', @type_covered.to_i, @algorithm.to_i, @labels)
267
+ msg.put_pack("NNN", @original_ttl, @expiration, @inception)
268
+ msg.put_pack("n", @key_tag)
269
+ msg.put_name(@signers_name, true)
270
+ }.to_s
271
+ return data
272
+ end
273
+ end
274
+ end
275
+ end