bluemonk-net-dns 0.5.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.
data/lib/net/dns/rr.rb ADDED
@@ -0,0 +1,406 @@
1
+ #
2
+ # $Id: RR.rb,v 1.19 2006/07/28 07:33:36 bluemonk Exp $
3
+ #
4
+
5
+ require 'net/dns/names/names'
6
+ require 'net/dns/rr/types'
7
+ require 'net/dns/rr/classes'
8
+
9
+
10
+ %w[a ns mx cname txt soa ptr aaaa mr].each do |file|
11
+ require "net/dns/rr/#{file}"
12
+ end
13
+
14
+ module Net # :nodoc:
15
+ module DNS
16
+
17
+ # =Name
18
+ #
19
+ # Net::DNS::RR - DNS Resource Record class
20
+ #
21
+ # =Synopsis
22
+ #
23
+ # require 'net/dns/rr'
24
+ #
25
+ # =Description
26
+ #
27
+ # The Net::DNS::RR is the base class for DNS Resource
28
+ # Record (RR) objects. A RR is a pack of data that represents
29
+ # resources for a DNS zone. The form in which this data is
30
+ # shows can be drawed as follow:
31
+ #
32
+ # "name ttl class type data"
33
+ #
34
+ # The +name+ is the name of the resource, like an canonical
35
+ # name for an +A+ record (internet ip address). The +ttl+ is the
36
+ # time to live, expressed in seconds. +type+ and +class+ are
37
+ # respectively the type of resource (+A+ for ip addresses, +NS+
38
+ # for nameservers, and so on) and the class, which is almost
39
+ # always +IN+, the Internet class. At the end, +data+ is the
40
+ # value associated to the name for that particular type of
41
+ # resource record. An example:
42
+ #
43
+ # # A record for IP address
44
+ # "www.example.com 86400 IN A 172.16.100.1"
45
+ #
46
+ # # NS record for name server
47
+ # "www.example.com 86400 IN NS ns.example.com"
48
+ #
49
+ # A new RR object can be created in 2 ways: passing a string
50
+ # such the ones above, or specifying each field as the pair
51
+ # of an hash. See the Net::DNS::RR.new method for details.
52
+ #
53
+ # =Error classes
54
+ #
55
+ # Some error classes has been defined for the Net::DNS::RR class,
56
+ # which are listed here to keep a light and browsable main documentation.
57
+ # We have:
58
+ #
59
+ # * RRArgumentError: Generic argument error for class Net::DNS::RR
60
+ # * RRDataError: Error in parsing binary data, maybe from a malformed packet
61
+ #
62
+ # =Copyright
63
+ #
64
+ # Copyright (c) 2006 Marco Ceresa
65
+ #
66
+ # All rights reserved. This program is free software; you may redistribute
67
+ # it and/or modify it under the same terms as Ruby itself.
68
+ #
69
+ class RR
70
+ include Net::DNS::Names
71
+
72
+ # Regexp matching an RR string
73
+ RR_REGEXP = Regexp.new("^\\s*(\\S+)\\s*(\\d+)?\\s+(" +
74
+ Net::DNS::RR::Classes.regexp +
75
+ "|CLASS\\d+)?\\s*(" +
76
+ Net::DNS::RR::Types.regexp +
77
+ "|TYPE\\d+)?\\s*(.*)$", Regexp::IGNORECASE)
78
+
79
+ # Dimension of the sum of class, type, TTL and rdlength fields in a
80
+ # RR portion of the packet, in bytes
81
+ RRFIXEDSZ = 10
82
+
83
+ # Name of the RR
84
+ attr_reader :name
85
+ # TTL time (in seconds) of the RR
86
+ attr_reader :ttl
87
+ # Data belonging to that appropriate class,
88
+ # not to be used (use real accessors instead)
89
+ attr_reader :rdata
90
+
91
+ # Create a new instance of Net::DNS::RR class, or an instance of
92
+ # any of the subclass of the appropriate type.
93
+ #
94
+ # Argument can be a string or an hash. With a sting, we can pass
95
+ # a RR resource record in the canonical format:
96
+ #
97
+ # a = Net::DNS::RR.new("foo.example.com. 86400 A 10.1.2.3")
98
+ # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
99
+ # cname = Net::DNS::RR.new("www.example.com 300 IN CNAME www1.example.com")
100
+ # txt = Net::DNS::RR.new('baz.example.com 3600 HS TXT "text record"')
101
+ #
102
+ # Incidentally, +a+, +mx+, +cname+ and +txt+ objects will be instances of
103
+ # respectively Net::DNS::RR::A, Net::DNS::RR::MX, Net::DNS::RR::CNAME and
104
+ # Net::DNS::RR::TXT classes.
105
+ #
106
+ # The name and RR data are required; all other informations are optional.
107
+ # If omitted, the +TTL+ defaults to 10800, +type+ default to +A+ and the RR class
108
+ # defaults to +IN+. Omitting the optional fields is useful for creating the
109
+ # empty RDATA sections required for certain dynamic update operations.
110
+ # All names must be fully qualified. The trailing dot (.) is optional.
111
+ #
112
+ # The preferred method is however passing an hash with keys and values:
113
+ #
114
+ # rr = Net::DNS::RR.new(
115
+ # :name => "foo.example.com",
116
+ # :ttl => 86400,
117
+ # :cls => "IN",
118
+ # :type => "A",
119
+ # :address => "10.1.2.3"
120
+ # )
121
+ #
122
+ # rr = Net::DNS::RR.new(
123
+ # :name => "foo.example.com",
124
+ # :rdata => "10.1.2.3"
125
+ # )
126
+ #
127
+ # Name and data are required; all the others fields are optionals like
128
+ # we've seen before. The data field can be specified either with the
129
+ # right name of the resource (+:address+ in the example above) or with
130
+ # the generic key +:rdata+. Consult documentation to find the exact name
131
+ # for the resource in each subclass.
132
+ #
133
+ def initialize(arg)
134
+ case arg
135
+ when String
136
+ instance = new_from_string(arg)
137
+ when Hash
138
+ instance = new_from_hash(arg)
139
+ else
140
+ raise RRArgumentError, "Invalid argument, must be a RR string or an hash of values"
141
+ end
142
+
143
+ if @type.to_s == "ANY"
144
+ @cls = Net::DNS::RR::Classes.new("IN")
145
+ end
146
+
147
+ build_pack
148
+ set_type
149
+
150
+ instance
151
+ end
152
+
153
+ # Return a new RR object of the correct type (like Net::DNS::RR::A
154
+ # if the type is A) from a binary string, usually obtained from
155
+ # network stream.
156
+ #
157
+ # This method is used when parsing a binary packet by the Packet
158
+ # class.
159
+ #
160
+ def RR.parse(data)
161
+ o = allocate
162
+ obj,offset = o.send(:new_from_binary, data, 0)
163
+ return obj
164
+ end
165
+
166
+ # Same as RR.parse, but takes an entire packet binary data to
167
+ # perform name expansion. Default when analizing a packet
168
+ # just received from a network stream.
169
+ #
170
+ # Return an instance of appropriate class and the offset
171
+ # pointing at the end of the data parsed.
172
+ #
173
+ def RR.parse_packet(data,offset)
174
+ o = allocate
175
+ o.send(:new_from_binary,data,offset)
176
+ end
177
+
178
+ # Return the RR object in binary data format, suitable
179
+ # for using in network streams, with names compressed.
180
+ # Must pass as arguments the offset inside the packet
181
+ # and an hash of compressed names.
182
+ #
183
+ # This method is to be used in other classes and is
184
+ # not intended for user space programs.
185
+ #
186
+ # TO FIX in one of the future releases
187
+ #
188
+ def comp_data(offset,compnames)
189
+ type,cls = @type.to_i, @cls.to_i
190
+ str,offset,names = dn_comp(@name,offset,compnames)
191
+ str += [type,cls,@ttl,@rdlength].pack("n2 N n")
192
+ offset += Net::DNS::RRFIXEDSZ
193
+ return str,offset,names
194
+ end
195
+
196
+ # Return the RR object in binary data format, suitable
197
+ # for using in network streams.
198
+ #
199
+ # raw_data = rr.data
200
+ # puts "RR is #{raw_data.size} bytes long"
201
+ #
202
+ def data
203
+ type,cls = @type.to_i, @cls.to_i
204
+ str = pack_name(@name)
205
+ return str + [type,cls,@ttl,@rdlength].pack("n2 N n") + get_data
206
+ end
207
+
208
+ # Canonical inspect method
209
+ #
210
+ # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
211
+ # #=> example.com. 7200 IN MX 10 mailhost.example.com.
212
+ #
213
+ def inspect
214
+ data = get_inspect
215
+ # Returns the preformatted string
216
+ if @name.size < 24
217
+ [@name, @ttl.to_s, @cls.to_s, @type.to_s,
218
+ data].pack("A24 A8 A8 A8 A*")
219
+ else
220
+ to_a.join(" ")
221
+ end
222
+ end
223
+
224
+ # Returns the RR in a string format.
225
+ #
226
+ # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
227
+ # mx.to_s
228
+ # #=> "example.com. 7200 IN MX 10 mailhost.example.com."
229
+ #
230
+ def to_s
231
+ "#{self.inspect}"
232
+ end
233
+
234
+ # Returns an array with all the fields for the RR record.
235
+ #
236
+ # mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
237
+ # mx.to_a
238
+ # #=> ["example.com.",7200,"IN","MX","10 mailhost.example.com."]
239
+ #
240
+ def to_a
241
+ [@name,@ttl,@cls.to_s,@type.to_s,get_inspect]
242
+ end
243
+
244
+ # Type accessor
245
+ def type
246
+ @type.to_s
247
+ end
248
+
249
+ # Class accessor
250
+ def cls
251
+ @cls.to_s
252
+ end
253
+
254
+ private
255
+
256
+ #---
257
+ # New RR with argument in string form
258
+ #---
259
+ def new_from_string(rrstring)
260
+
261
+ unless rrstring =~ RR_REGEXP
262
+ raise RRArgumentError,
263
+ "Format error for RR string (maybe CLASS and TYPE not valid?)"
264
+ end
265
+
266
+ # Name of RR - mandatory
267
+ begin
268
+ @name = $1.downcase
269
+ rescue NoMethodError
270
+ raise RRArgumentError, "Missing name field in RR string #{rrstring}"
271
+ end
272
+
273
+ # Time to live for RR, default 3 hours
274
+ @ttl = $2 ? $2.to_i : 10800
275
+
276
+ # RR class, default to IN
277
+ @cls = Net::DNS::RR::Classes.new $3
278
+
279
+ # RR type, default to A
280
+ @type = Net::DNS::RR::Types.new $4
281
+
282
+ # All the rest is data
283
+ @rdata = $5 ? $5.strip : ""
284
+
285
+ if self.class == Net::DNS::RR
286
+ (eval "Net::DNS::RR::#@type").new(rrstring)
287
+ else
288
+ subclass_new_from_string(@rdata)
289
+ self.class
290
+ end
291
+ end
292
+
293
+ def new_from_hash(args)
294
+
295
+ # Name field is mandatory
296
+ unless args.has_key? :name
297
+ raise RRArgumentError, "RR argument error: need at least RR name"
298
+ end
299
+
300
+ @name = args[:name].downcase
301
+ @ttl = args[:ttl] ? args[:ttl].to_i : 10800 # Default 3 hours
302
+ @type = Net::DNS::RR::Types.new args[:type]
303
+ @cls = Net::DNS::RR::Classes.new args[:cls]
304
+
305
+ @rdata = args[:rdata] ? args[:rdata].strip : ""
306
+ @rdlength = args[:rdlength] || @rdata.size
307
+
308
+ if self.class == Net::DNS::RR
309
+ (eval "Net::DNS::RR::#@type").new(args)
310
+ else
311
+ hash = args - [:name,:ttl,:type,:cls]
312
+ if hash.has_key? :rdata
313
+ subclass_new_from_string(hash[:rdata])
314
+ else
315
+ subclass_new_from_hash(hash)
316
+ end
317
+ self.class
318
+ end
319
+ end # new_from_hash
320
+
321
+ def new_from_binary(data,offset)
322
+ if self.class == Net::DNS::RR
323
+ temp = dn_expand(data,offset)[1]
324
+ type = Net::DNS::RR::Types.new data.unpack("@#{temp} n")[0]
325
+ (eval "Net::DNS::RR::#{type}").parse_packet(data,offset)
326
+ else
327
+ @name,offset = dn_expand(data,offset)
328
+ rrtype,cls,@ttl,@rdlength = data.unpack("@#{offset} n2 N n")
329
+ @type = Net::DNS::RR::Types.new rrtype
330
+ @cls = Net::DNS::RR::Classes.new cls
331
+ offset += RRFIXEDSZ
332
+ offset = subclass_new_from_binary(data,offset)
333
+ build_pack
334
+ set_type
335
+ return [self,offset]
336
+ end
337
+ # rescue StandardError => err
338
+ # raise RRDataError, "Caught exception, maybe packet malformed: #{err}"
339
+ end
340
+
341
+ # Methods to be overridden by subclasses
342
+ def subclass_new_from_array(arr)
343
+ end
344
+ def subclass_new_from_string(str)
345
+ end
346
+ def subclass_new_from_hash(hash)
347
+ end
348
+ def subclass_new_from_binary(data,offset)
349
+ end
350
+ def build_pack
351
+ end
352
+ def set_type
353
+ end
354
+ def get_inspect
355
+ @rdata
356
+ end
357
+ def get_data
358
+ @rdata
359
+ end
360
+
361
+ # NEW new method :)
362
+ def self.new(*args)
363
+ o = allocate
364
+ obj = o.send(:initialize,*args)
365
+ if self == Net::DNS::RR
366
+ return obj
367
+ else
368
+ return o
369
+ end
370
+ end
371
+
372
+ end # class RR
373
+
374
+ end # module DNS
375
+ end # module Net
376
+
377
+ class RRArgumentError < ArgumentError # :nodoc:
378
+ end
379
+ class RRDataError < StandardError # :nodoc:
380
+ end
381
+
382
+ module ExtendHash # :nodoc:
383
+
384
+ # Performs a sort of group difference
385
+ # operation on hashes or arrays
386
+ #
387
+ # a = {:a=>1,:b=>2,:c=>3}
388
+ # b = {:a=>1,:b=>2}
389
+ # c = [:a,:c]
390
+ # a-b #=> {:c=>3}
391
+ # a-c #=> {:b=>2}
392
+ #
393
+ def -(oth)
394
+ case oth
395
+ when Hash
396
+ delete_if {|k,v| oth.has_key? k}
397
+ when Array
398
+ delete_if {|k,v| oth.include? k}
399
+ end
400
+ end
401
+ end
402
+
403
+ class Hash # :nodoc:
404
+ include ExtendHash
405
+ end
406
+