bluemonk-net-dns 0.5.0

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