logmerge 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1892 @@
1
+ ##
2
+ # This resolv.rb is identical to 1.8.4's, but has the added ability of
3
+ # returning the TTL somewhere down inside. (I forget exactly where.)
4
+
5
+ =begin
6
+ = resolv library
7
+ resolv.rb is a resolver library written in Ruby.
8
+ Since it is written in Ruby, it is thread-aware.
9
+ I.e. it can resolv many hostnames concurrently.
10
+
11
+ It is possible to lookup various resources of DNS using DNS module directly.
12
+
13
+ == example
14
+ p Resolv.getaddress("www.ruby-lang.org")
15
+ p Resolv.getname("210.251.121.214")
16
+
17
+ Resolv::DNS.open {|dns|
18
+ p dns.getresources("www.ruby-lang.org", Resolv::DNS::Resource::IN::A).collect {|r| r.address}
19
+ p dns.getresources("ruby-lang.org", Resolv::DNS::Resource::IN::MX).collect {|r| [r.exchange.to_s, r.preference]}
20
+ }
21
+
22
+ == Resolv class
23
+
24
+ === class methods
25
+ --- Resolv.getaddress(name)
26
+ --- Resolv.getaddresses(name)
27
+ --- Resolv.each_address(name) {|address| ...}
28
+ They lookups IP addresses of ((|name|)) which represents a hostname
29
+ as a string by default resolver.
30
+
31
+ getaddress returns first entry of lookupped addresses.
32
+ getaddresses returns lookupped addresses as an array.
33
+ each_address iterates over lookupped addresses.
34
+
35
+ --- Resolv.getname(address)
36
+ --- Resolv.getnames(address)
37
+ --- Resolv.each_name(address) {|name| ...}
38
+ lookups hostnames of ((|address|)) which represents IP address as a string.
39
+
40
+ getname returns first entry of lookupped names.
41
+ getnames returns lookupped names as an array.
42
+ each_names iterates over lookupped names.
43
+
44
+ == Resolv::Hosts class
45
+ hostname resolver using /etc/hosts format.
46
+
47
+ === class methods
48
+ --- Resolv::Hosts.new(hosts='/etc/hosts')
49
+
50
+ === methods
51
+ --- Resolv::Hosts#getaddress(name)
52
+ --- Resolv::Hosts#getaddresses(name)
53
+ --- Resolv::Hosts#each_address(name) {|address| ...}
54
+ address lookup methods.
55
+
56
+ --- Resolv::Hosts#getname(address)
57
+ --- Resolv::Hosts#getnames(address)
58
+ --- Resolv::Hosts#each_name(address) {|name| ...}
59
+ hostnames lookup methods.
60
+
61
+ == Resolv::DNS class
62
+ DNS stub resolver.
63
+
64
+ === class methods
65
+ --- Resolv::DNS.new(config_info=nil)
66
+
67
+ ((|config_info|)) should be nil, a string or a hash.
68
+ If nil is given, /etc/resolv.conf and platform specific information is used.
69
+ If a string is given, it should be a filename which format is same as /etc/resolv.conf.
70
+ If a hash is given, it may contains information for nameserver, search and ndots as follows.
71
+
72
+ Resolv::DNS.new({:nameserver=>["210.251.121.21"], :search=>["ruby-lang.org"], :ndots=>1})
73
+
74
+ --- Resolv::DNS.open(config_info=nil)
75
+ --- Resolv::DNS.open(config_info=nil) {|dns| ...}
76
+
77
+ === methods
78
+ --- Resolv::DNS#close
79
+
80
+ --- Resolv::DNS#getaddress(name)
81
+ --- Resolv::DNS#getaddresses(name)
82
+ --- Resolv::DNS#each_address(name) {|address| ...}
83
+ address lookup methods.
84
+
85
+ ((|name|)) must be a instance of Resolv::DNS::Name or String. Lookupped
86
+ address is represented as an instance of Resolv::IPv4 or Resolv::IPv6.
87
+
88
+ --- Resolv::DNS#getname(address)
89
+ --- Resolv::DNS#getnames(address)
90
+ --- Resolv::DNS#each_name(address) {|name| ...}
91
+ hostnames lookup methods.
92
+
93
+ ((|address|)) must be a instance of Resolv::IPv4, Resolv::IPv6 or String.
94
+ Lookupped name is represented as an instance of Resolv::DNS::Name.
95
+
96
+ --- Resolv::DNS#getresource(name, typeclass)
97
+ --- Resolv::DNS#getresources(name, typeclass)
98
+ --- Resolv::DNS#each_resource(name, typeclass) {|resource| ...}
99
+ They lookup DNS resources of ((|name|)).
100
+ ((|name|)) must be a instance of Resolv::Name or String.
101
+
102
+ ((|typeclass|)) should be one of follows:
103
+ * Resolv::DNS::Resource::IN::ANY
104
+ * Resolv::DNS::Resource::IN::NS
105
+ * Resolv::DNS::Resource::IN::CNAME
106
+ * Resolv::DNS::Resource::IN::SOA
107
+ * Resolv::DNS::Resource::IN::HINFO
108
+ * Resolv::DNS::Resource::IN::MINFO
109
+ * Resolv::DNS::Resource::IN::MX
110
+ * Resolv::DNS::Resource::IN::TXT
111
+ * Resolv::DNS::Resource::IN::ANY
112
+ * Resolv::DNS::Resource::IN::A
113
+ * Resolv::DNS::Resource::IN::WKS
114
+ * Resolv::DNS::Resource::IN::PTR
115
+ * Resolv::DNS::Resource::IN::AAAA
116
+
117
+ Lookupped resource is represented as an instance of (a subclass of)
118
+ Resolv::DNS::Resource.
119
+ (Resolv::DNS::Resource::IN::A, etc.)
120
+
121
+ == Resolv::DNS::Resource::IN::NS class
122
+ --- name
123
+ == Resolv::DNS::Resource::IN::CNAME class
124
+ --- name
125
+ == Resolv::DNS::Resource::IN::SOA class
126
+ --- mname
127
+ --- rname
128
+ --- serial
129
+ --- refresh
130
+ --- retry
131
+ --- expire
132
+ --- minimum
133
+ == Resolv::DNS::Resource::IN::HINFO class
134
+ --- cpu
135
+ --- os
136
+ == Resolv::DNS::Resource::IN::MINFO class
137
+ --- rmailbx
138
+ --- emailbx
139
+ == Resolv::DNS::Resource::IN::MX class
140
+ --- preference
141
+ --- exchange
142
+ == Resolv::DNS::Resource::IN::TXT class
143
+ --- data
144
+ == Resolv::DNS::Resource::IN::A class
145
+ --- address
146
+ == Resolv::DNS::Resource::IN::WKS class
147
+ --- address
148
+ --- protocol
149
+ --- bitmap
150
+ == Resolv::DNS::Resource::IN::PTR class
151
+ --- name
152
+ == Resolv::DNS::Resource::IN::AAAA class
153
+ --- address
154
+
155
+ == Resolv::DNS::Name class
156
+
157
+ === class methods
158
+ --- Resolv::DNS::Name.create(name)
159
+
160
+ === methods
161
+ --- Resolv::DNS::Name#to_s
162
+
163
+ == Resolv::DNS::Resource class
164
+
165
+ == Resolv::IPv4 class
166
+ === class methods
167
+ --- Resolv::IPv4.create(address)
168
+
169
+ === methods
170
+ --- Resolv::IPv4#to_s
171
+ --- Resolv::IPv4#to_name
172
+
173
+ === constants
174
+ --- Resolv::IPv4::Regex
175
+ regular expression for IPv4 address.
176
+
177
+ == Resolv::IPv6 class
178
+ === class methods
179
+ --- Resolv::IPv6.create(address)
180
+
181
+ === methods
182
+ --- Resolv::IPv6#to_s
183
+ --- Resolv::IPv6#to_name
184
+
185
+ === constants
186
+ --- Resolv::IPv6::Regex
187
+ regular expression for IPv6 address.
188
+
189
+ == Bugs
190
+ * NIS is not supported.
191
+ * /etc/nsswitch.conf is not supported.
192
+ * IPv6 is not supported.
193
+
194
+ =end
195
+
196
+ require 'socket'
197
+ require 'fcntl'
198
+ require 'timeout'
199
+ require 'thread'
200
+
201
+ class Resolv
202
+ def self.getaddress(name)
203
+ DefaultResolver.getaddress(name)
204
+ end
205
+
206
+ def self.getaddresses(name)
207
+ DefaultResolver.getaddresses(name)
208
+ end
209
+
210
+ def self.each_address(name, &block)
211
+ DefaultResolver.each_address(name, &block)
212
+ end
213
+
214
+ def self.getname(address)
215
+ DefaultResolver.getname(address)
216
+ end
217
+
218
+ def self.getnames(address)
219
+ DefaultResolver.getnames(address)
220
+ end
221
+
222
+ def self.each_name(address, &proc)
223
+ DefaultResolver.each_name(address, &proc)
224
+ end
225
+
226
+ def initialize(resolvers=[Hosts.new, DNS.new])
227
+ @resolvers = resolvers
228
+ end
229
+
230
+ def getaddress(name)
231
+ each_address(name) {|address| return address}
232
+ raise ResolvError.new("no address for #{name}")
233
+ end
234
+
235
+ def getaddresses(name)
236
+ ret = []
237
+ each_address(name) {|address| ret << address}
238
+ return ret
239
+ end
240
+
241
+ def each_address(name)
242
+ if AddressRegex =~ name
243
+ yield name
244
+ return
245
+ end
246
+ yielded = false
247
+ @resolvers.each {|r|
248
+ r.each_address(name) {|address|
249
+ yield address.to_s
250
+ yielded = true
251
+ }
252
+ return if yielded
253
+ }
254
+ end
255
+
256
+ def getname(address)
257
+ each_name(address) {|name| return name}
258
+ raise ResolvError.new("no name for #{address}")
259
+ end
260
+
261
+ def getnames(address)
262
+ ret = []
263
+ each_name(address) {|name| ret << name}
264
+ return ret
265
+ end
266
+
267
+ def each_name(address)
268
+ yielded = false
269
+ @resolvers.each {|r|
270
+ r.each_name(address) {|name|
271
+ yield name.to_s
272
+ yielded = true
273
+ }
274
+ return if yielded
275
+ }
276
+ end
277
+
278
+ class ResolvError < StandardError
279
+ end
280
+
281
+ class ResolvTimeout < TimeoutError
282
+ end
283
+
284
+ class Hosts
285
+ if /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
286
+ require 'win32/resolv'
287
+ DefaultFileName = Win32::Resolv.get_hosts_path
288
+ else
289
+ DefaultFileName = '/etc/hosts'
290
+ end
291
+
292
+ def initialize(filename = DefaultFileName)
293
+ @filename = filename
294
+ @mutex = Mutex.new
295
+ @initialized = nil
296
+ end
297
+
298
+ def lazy_initialize
299
+ @mutex.synchronize {
300
+ unless @initialized
301
+ @name2addr = {}
302
+ @addr2name = {}
303
+ open(@filename) {|f|
304
+ f.each {|line|
305
+ line.sub!(/#.*/, '')
306
+ addr, hostname, *aliases = line.split(/\s+/)
307
+ next unless addr
308
+ addr.untaint
309
+ hostname.untaint
310
+ @addr2name[addr] = [] unless @addr2name.include? addr
311
+ @addr2name[addr] << hostname
312
+ @addr2name[addr] += aliases
313
+ @name2addr[hostname] = [] unless @name2addr.include? hostname
314
+ @name2addr[hostname] << addr
315
+ aliases.each {|n|
316
+ n.untaint
317
+ @name2addr[n] = [] unless @name2addr.include? n
318
+ @name2addr[n] << addr
319
+ }
320
+ }
321
+ }
322
+ @name2addr.each {|name, arr| arr.reverse!}
323
+ @initialized = true
324
+ end
325
+ }
326
+ self
327
+ end
328
+
329
+ def getaddress(name)
330
+ each_address(name) {|address| return address}
331
+ raise ResolvError.new("#{@filename} has no name: #{name}")
332
+ end
333
+
334
+ def getaddresses(name)
335
+ ret = []
336
+ each_address(name) {|address| ret << address}
337
+ return ret
338
+ end
339
+
340
+ def each_address(name, &proc)
341
+ lazy_initialize
342
+ if @name2addr.include?(name)
343
+ @name2addr[name].each(&proc)
344
+ end
345
+ end
346
+
347
+ def getname(address)
348
+ each_name(address) {|name| return name}
349
+ raise ResolvError.new("#{@filename} has no address: #{address}")
350
+ end
351
+
352
+ def getnames(address)
353
+ ret = []
354
+ each_name(address) {|name| ret << name}
355
+ return ret
356
+ end
357
+
358
+ def each_name(address, &proc)
359
+ lazy_initialize
360
+ if @addr2name.include?(address)
361
+ @addr2name[address].each(&proc)
362
+ end
363
+ end
364
+ end
365
+
366
+ class DNS
367
+ # STD0013 (RFC 1035, etc.)
368
+ # ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
369
+
370
+ Port = 53
371
+ UDPSize = 512
372
+
373
+ DNSThreadGroup = ThreadGroup.new
374
+
375
+ def self.open(*args)
376
+ dns = new(*args)
377
+ return dns unless block_given?
378
+ begin
379
+ yield dns
380
+ ensure
381
+ dns.close
382
+ end
383
+ end
384
+
385
+ def initialize(config_info=nil)
386
+ @mutex = Mutex.new
387
+ @config = Config.new(config_info)
388
+ @initialized = nil
389
+ end
390
+
391
+ def lazy_initialize
392
+ @mutex.synchronize {
393
+ unless @initialized
394
+ @config.lazy_initialize
395
+
396
+ if nameserver = @config.single?
397
+ @requester = Requester::ConnectedUDP.new(nameserver)
398
+ else
399
+ @requester = Requester::UnconnectedUDP.new
400
+ end
401
+
402
+ @initialized = true
403
+ end
404
+ }
405
+ self
406
+ end
407
+
408
+ def close
409
+ @mutex.synchronize {
410
+ if @initialized
411
+ @requester.close if @requester
412
+ @requester = nil
413
+ @initialized = false
414
+ end
415
+ }
416
+ end
417
+
418
+ def getaddress(name)
419
+ each_address(name) {|address| return address}
420
+ raise ResolvError.new("DNS result has no information for #{name}")
421
+ end
422
+
423
+ def getaddresses(name)
424
+ ret = []
425
+ each_address(name) {|address| ret << address}
426
+ return ret
427
+ end
428
+
429
+ def each_address(name)
430
+ each_resource(name, Resource::IN::A) {|resource| yield resource.address}
431
+ end
432
+
433
+ def getname(address)
434
+ each_name(address) {|name| return name}
435
+ raise ResolvError.new("DNS result has no information for #{address}")
436
+ end
437
+
438
+ def getnames(address)
439
+ ret = []
440
+ each_name(address) {|name| ret << name}
441
+ return ret
442
+ end
443
+
444
+ def each_name(address)
445
+ case address
446
+ when Name
447
+ ptr = address
448
+ when IPv4::Regex
449
+ ptr = IPv4.create(address).to_name
450
+ when IPv6::Regex
451
+ ptr = IPv6.create(address).to_name
452
+ else
453
+ raise ResolvError.new("cannot interpret as address: #{address}")
454
+ end
455
+ each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name}
456
+ end
457
+
458
+ def getresource(name, typeclass)
459
+ each_resource(name, typeclass) {|resource| return resource}
460
+ raise ResolvError.new("DNS result has no information for #{name}")
461
+ end
462
+
463
+ def getresources(name, typeclass)
464
+ ret = []
465
+ each_resource(name, typeclass) {|resource| ret << resource}
466
+ return ret
467
+ end
468
+
469
+ def each_resource(name, typeclass, &proc)
470
+ lazy_initialize
471
+ q = Queue.new
472
+ senders = {}
473
+ begin
474
+ @config.resolv(name) {|candidate, tout, nameserver|
475
+ msg = Message.new
476
+ msg.rd = 1
477
+ msg.add_question(candidate, typeclass)
478
+ unless sender = senders[[candidate, nameserver]]
479
+ sender = senders[[candidate, nameserver]] =
480
+ @requester.sender(msg, candidate, q, nameserver)
481
+ end
482
+ sender.send
483
+ reply = reply_name = nil
484
+ timeout(tout, ResolvTimeout) { reply, reply_name = q.pop }
485
+ case reply.rcode
486
+ when RCode::NoError
487
+ extract_resources(reply, reply_name, typeclass, &proc)
488
+ return
489
+ when RCode::NXDomain
490
+ raise Config::NXDomain.new(reply_name.to_s)
491
+ else
492
+ raise Config::OtherResolvError.new(reply_name.to_s)
493
+ end
494
+ }
495
+ ensure
496
+ @requester.delete(q)
497
+ end
498
+ end
499
+
500
+ def extract_resources(msg, name, typeclass)
501
+ if typeclass < Resource::ANY
502
+ n0 = Name.create(name)
503
+ msg.each_answer {|n, ttl, data|
504
+ yield data if n0 == n
505
+ }
506
+ end
507
+ yielded = false
508
+ n0 = Name.create(name)
509
+ msg.each_answer {|n, ttl, data|
510
+ if n0 == n
511
+ case data
512
+ when typeclass
513
+ yield data
514
+ yielded = true
515
+ when Resource::CNAME
516
+ n0 = data.name
517
+ end
518
+ end
519
+ }
520
+ return if yielded
521
+ msg.each_answer {|n, ttl, data|
522
+ if n0 == n
523
+ case data
524
+ when typeclass
525
+ yield data
526
+ end
527
+ end
528
+ }
529
+ end
530
+
531
+ class Requester
532
+ def initialize
533
+ @senders = {}
534
+ end
535
+
536
+ def close
537
+ thread, sock, @thread, @sock = @thread, @sock
538
+ begin
539
+ if thread
540
+ thread.kill
541
+ thread.join
542
+ end
543
+ ensure
544
+ sock.close if sock
545
+ end
546
+ end
547
+
548
+ def delete(arg)
549
+ case arg
550
+ when Sender
551
+ @senders.delete_if {|k, s| s == arg }
552
+ when Queue
553
+ @senders.delete_if {|k, s| s.queue == arg }
554
+ else
555
+ raise ArgumentError.new("neither Sender or Queue: #{arg}")
556
+ end
557
+ end
558
+
559
+ class Sender
560
+ def initialize(msg, data, sock, queue)
561
+ @msg = msg
562
+ @data = data
563
+ @sock = sock
564
+ @queue = queue
565
+ end
566
+ attr_reader :queue
567
+
568
+ def recv(msg)
569
+ @queue.push([msg, @data])
570
+ end
571
+ end
572
+
573
+ class UnconnectedUDP < Requester
574
+ def initialize
575
+ super()
576
+ @sock = UDPSocket.new
577
+ @sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
578
+ @id = {}
579
+ @id.default = -1
580
+ @thread = Thread.new {
581
+ DNSThreadGroup.add Thread.current
582
+ loop {
583
+ reply, from = @sock.recvfrom(UDPSize)
584
+ msg = begin
585
+ Message.decode(reply)
586
+ rescue DecodeError
587
+ STDERR.print("DNS message decoding error: #{reply.inspect}\n")
588
+ next
589
+ end
590
+ if s = @senders[[[from[3],from[1]],msg.id]]
591
+ s.recv msg
592
+ else
593
+ #STDERR.print("non-handled DNS message: #{msg.inspect} from #{from.inspect}\n")
594
+ end
595
+ }
596
+ }
597
+ end
598
+
599
+ def sender(msg, data, queue, host, port=Port)
600
+ service = [host, port]
601
+ id = Thread.exclusive {
602
+ @id[service] = (@id[service] + 1) & 0xffff
603
+ }
604
+ request = msg.encode
605
+ request[0,2] = [id].pack('n')
606
+ return @senders[[service, id]] =
607
+ Sender.new(request, data, @sock, host, port, queue)
608
+ end
609
+
610
+ class Sender < Requester::Sender
611
+ def initialize(msg, data, sock, host, port, queue)
612
+ super(msg, data, sock, queue)
613
+ @host = host
614
+ @port = port
615
+ end
616
+
617
+ def send
618
+ @sock.send(@msg, 0, @host, @port)
619
+ end
620
+ end
621
+ end
622
+
623
+ class ConnectedUDP < Requester
624
+ def initialize(host, port=Port)
625
+ super()
626
+ @host = host
627
+ @port = port
628
+ @sock = UDPSocket.new
629
+ @sock.connect(host, port)
630
+ @sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
631
+ @id = -1
632
+ @thread = Thread.new {
633
+ DNSThreadGroup.add Thread.current
634
+ loop {
635
+ reply = @sock.recv(UDPSize)
636
+ msg = begin
637
+ Message.decode(reply)
638
+ rescue DecodeError
639
+ STDERR.print("DNS message decoding error: #{reply.inspect}")
640
+ next
641
+ end
642
+ if s = @senders[msg.id]
643
+ s.recv msg
644
+ else
645
+ #STDERR.print("non-handled DNS message: #{msg.inspect}")
646
+ end
647
+ }
648
+ }
649
+ end
650
+
651
+ def sender(msg, data, queue, host=@host, port=@port)
652
+ unless host == @host && port == @port
653
+ raise RequestError.new("host/port don't match: #{host}:#{port}")
654
+ end
655
+ id = Thread.exclusive { @id = (@id + 1) & 0xffff }
656
+ request = msg.encode
657
+ request[0,2] = [id].pack('n')
658
+ return @senders[id] = Sender.new(request, data, @sock, queue)
659
+ end
660
+
661
+ class Sender < Requester::Sender
662
+ def send
663
+ @sock.send(@msg, 0)
664
+ end
665
+ end
666
+ end
667
+
668
+ class TCP < Requester
669
+ def initialize(host, port=Port)
670
+ super()
671
+ @host = host
672
+ @port = port
673
+ @sock = TCPSocket.new
674
+ @sock.connect(host, port)
675
+ @sock.fcntl(Fcntl::F_SETFD, 1) if defined? Fcntl::F_SETFD
676
+ @id = -1
677
+ @senders = {}
678
+ @thread = Thread.new {
679
+ DNSThreadGroup.add Thread.current
680
+ loop {
681
+ len = @sock.read(2).unpack('n')
682
+ reply = @sock.read(len)
683
+ msg = begin
684
+ Message.decode(reply)
685
+ rescue DecodeError
686
+ STDERR.print("DNS message decoding error: #{reply.inspect}")
687
+ next
688
+ end
689
+ if s = @senders[msg.id]
690
+ s.push msg
691
+ else
692
+ #STDERR.print("non-handled DNS message: #{msg.inspect}")
693
+ end
694
+ }
695
+ }
696
+ end
697
+
698
+ def sender(msg, data, queue, host=@host, port=@port)
699
+ unless host == @host && port == @port
700
+ raise RequestError.new("host/port don't match: #{host}:#{port}")
701
+ end
702
+ id = Thread.exclusive { @id = (@id + 1) & 0xffff }
703
+ request = msg.encode
704
+ request[0,2] = [request.length, id].pack('nn')
705
+ return @senders[id] = Sender.new(request, data, @sock, queue)
706
+ end
707
+
708
+ class Sender < Requester::Sender
709
+ def send
710
+ @sock.print(@msg)
711
+ @sock.flush
712
+ end
713
+ end
714
+ end
715
+
716
+ class RequestError < StandardError
717
+ end
718
+ end
719
+
720
+ class Config
721
+ def initialize(config_info=nil)
722
+ @mutex = Mutex.new
723
+ @config_info = config_info
724
+ @initialized = nil
725
+ end
726
+
727
+ def Config.parse_resolv_conf(filename)
728
+ nameserver = []
729
+ search = nil
730
+ ndots = 1
731
+ open(filename) {|f|
732
+ f.each {|line|
733
+ line.sub!(/[#;].*/, '')
734
+ keyword, *args = line.split(/\s+/)
735
+ args.each { |arg|
736
+ arg.untaint
737
+ }
738
+ next unless keyword
739
+ case keyword
740
+ when 'nameserver'
741
+ nameserver += args
742
+ when 'domain'
743
+ next if args.empty?
744
+ search = [args[0]]
745
+ when 'search'
746
+ next if args.empty?
747
+ search = args
748
+ when 'options'
749
+ args.each {|arg|
750
+ case arg
751
+ when /\Andots:(\d+)\z/
752
+ ndots = $1.to_i
753
+ end
754
+ }
755
+ end
756
+ }
757
+ }
758
+ return { :nameserver => nameserver, :search => search, :ndots => ndots }
759
+ end
760
+
761
+ def Config.default_config_hash(filename="/etc/resolv.conf")
762
+ if File.exist? filename
763
+ config_hash = Config.parse_resolv_conf(filename)
764
+ else
765
+ if /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
766
+ search, nameserver = Win32::Resolv.get_resolv_info
767
+ config_hash = {}
768
+ config_hash[:nameserver] = nameserver if nameserver
769
+ config_hash[:search] = [search].flatten if search
770
+ end
771
+ end
772
+ config_hash
773
+ end
774
+
775
+ def lazy_initialize
776
+ @mutex.synchronize {
777
+ unless @initialized
778
+ @nameserver = []
779
+ @search = nil
780
+ @ndots = 1
781
+ case @config_info
782
+ when nil
783
+ config_hash = Config.default_config_hash
784
+ when String
785
+ config_hash = Config.parse_resolv_conf(@config_info)
786
+ when Hash
787
+ config_hash = @config_info.dup
788
+ if String === config_hash[:nameserver]
789
+ config_hash[:nameserver] = [config_hash[:nameserver]]
790
+ end
791
+ if String === config_hash[:search]
792
+ config_hash[:search] = [config_hash[:search]]
793
+ end
794
+ else
795
+ raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}")
796
+ end
797
+ @nameserver = config_hash[:nameserver] if config_hash.include? :nameserver
798
+ @search = config_hash[:search] if config_hash.include? :search
799
+ @ndots = config_hash[:ndots] if config_hash.include? :ndots
800
+
801
+ @nameserver = ['0.0.0.0'] if @nameserver.empty?
802
+ if @search
803
+ @search = @search.map {|arg| Label.split(arg) }
804
+ else
805
+ hostname = Socket.gethostname
806
+ if /\./ =~ hostname
807
+ @search = [Label.split($')]
808
+ else
809
+ @search = [[]]
810
+ end
811
+ end
812
+
813
+ if !@nameserver.kind_of?(Array) ||
814
+ !@nameserver.all? {|ns| String === ns }
815
+ raise ArgumentError.new("invalid nameserver config: #{@nameserver.inspect}")
816
+ end
817
+
818
+ if !@search.kind_of?(Array) ||
819
+ !@search.all? {|ls| ls.all? {|l| Label::Str === l } }
820
+ raise ArgumentError.new("invalid search config: #{@search.inspect}")
821
+ end
822
+
823
+ if !@ndots.kind_of?(Integer)
824
+ raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}")
825
+ end
826
+
827
+ @initialized = true
828
+ end
829
+ }
830
+ self
831
+ end
832
+
833
+ def single?
834
+ lazy_initialize
835
+ if @nameserver.length == 1
836
+ return @nameserver[0]
837
+ else
838
+ return nil
839
+ end
840
+ end
841
+
842
+ def generate_candidates(name)
843
+ candidates = nil
844
+ name = Name.create(name)
845
+ if name.absolute?
846
+ candidates = [name]
847
+ else
848
+ if @ndots <= name.length - 1
849
+ candidates = [Name.new(name.to_a)]
850
+ else
851
+ candidates = []
852
+ end
853
+ candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
854
+ end
855
+ return candidates
856
+ end
857
+
858
+ InitialTimeout = 5
859
+
860
+ def generate_timeouts
861
+ ts = [InitialTimeout]
862
+ ts << ts[-1] * 2 / @nameserver.length
863
+ ts << ts[-1] * 2
864
+ ts << ts[-1] * 2
865
+ return ts
866
+ end
867
+
868
+ def resolv(name)
869
+ candidates = generate_candidates(name)
870
+ timeouts = generate_timeouts
871
+ begin
872
+ candidates.each {|candidate|
873
+ begin
874
+ timeouts.each {|tout|
875
+ @nameserver.each {|nameserver|
876
+ begin
877
+ yield candidate, tout, nameserver
878
+ rescue ResolvTimeout
879
+ end
880
+ }
881
+ }
882
+ raise ResolvError.new("DNS resolv timeout: #{name}")
883
+ rescue NXDomain
884
+ end
885
+ }
886
+ rescue ResolvError
887
+ end
888
+ end
889
+
890
+ class NXDomain < ResolvError
891
+ end
892
+
893
+ class OtherResolvError < ResolvError
894
+ end
895
+ end
896
+
897
+ module OpCode
898
+ Query = 0
899
+ IQuery = 1
900
+ Status = 2
901
+ Notify = 4
902
+ Update = 5
903
+ end
904
+
905
+ module RCode
906
+ NoError = 0
907
+ FormErr = 1
908
+ ServFail = 2
909
+ NXDomain = 3
910
+ NotImp = 4
911
+ Refused = 5
912
+ YXDomain = 6
913
+ YXRRSet = 7
914
+ NXRRSet = 8
915
+ NotAuth = 9
916
+ NotZone = 10
917
+ BADVERS = 16
918
+ BADSIG = 16
919
+ BADKEY = 17
920
+ BADTIME = 18
921
+ BADMODE = 19
922
+ BADNAME = 20
923
+ BADALG = 21
924
+ end
925
+
926
+ class DecodeError < StandardError
927
+ end
928
+
929
+ class EncodeError < StandardError
930
+ end
931
+
932
+ module Label
933
+ def self.split(arg)
934
+ labels = []
935
+ arg.scan(/[^\.]+/) {labels << Str.new($&)}
936
+ return labels
937
+ end
938
+
939
+ class Str
940
+ def initialize(string)
941
+ @string = string
942
+ @downcase = string.downcase
943
+ end
944
+ attr_reader :string, :downcase
945
+
946
+ def to_s
947
+ return @string
948
+ end
949
+
950
+ def inspect
951
+ return "#<#{self.class} #{self.to_s}>"
952
+ end
953
+
954
+ def ==(other)
955
+ return @downcase == other.downcase
956
+ end
957
+
958
+ def eql?(other)
959
+ return self == other
960
+ end
961
+
962
+ def hash
963
+ return @downcase.hash
964
+ end
965
+ end
966
+ end
967
+
968
+ class Name
969
+ def self.create(arg)
970
+ case arg
971
+ when Name
972
+ return arg
973
+ when String
974
+ return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
975
+ else
976
+ raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
977
+ end
978
+ end
979
+
980
+ def initialize(labels, absolute=true)
981
+ @labels = labels
982
+ @absolute = absolute
983
+ end
984
+
985
+ def inspect
986
+ "#<#{self.class}: #{self.to_s}#{@absolute ? '.' : ''}>"
987
+ end
988
+
989
+ def absolute?
990
+ return @absolute
991
+ end
992
+
993
+ def ==(other)
994
+ return false unless Name === other
995
+ return @labels == other.to_a && @absolute == other.absolute?
996
+ end
997
+ alias eql? ==
998
+
999
+ # tests subdomain-of relation.
1000
+ #
1001
+ # domain = Resolv::DNS::Name.create("y.z")
1002
+ # p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true
1003
+ # p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true
1004
+ # p Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false
1005
+ # p Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false
1006
+ # p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false
1007
+ # p Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false
1008
+ #
1009
+ def subdomain_of?(other)
1010
+ raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other
1011
+ return false if @absolute != other.absolute?
1012
+ other_len = other.length
1013
+ return false if @labels.length <= other_len
1014
+ return @labels[-other_len, other_len] == other.to_a
1015
+ end
1016
+
1017
+ def hash
1018
+ return @labels.hash ^ @absolute.hash
1019
+ end
1020
+
1021
+ def to_a
1022
+ return @labels
1023
+ end
1024
+
1025
+ def length
1026
+ return @labels.length
1027
+ end
1028
+
1029
+ def [](i)
1030
+ return @labels[i]
1031
+ end
1032
+
1033
+ # returns the domain name as a string.
1034
+ #
1035
+ # The domain name doesn't have a trailing dot even if the name object is
1036
+ # absolute.
1037
+ #
1038
+ # p Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z"
1039
+ # p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z"
1040
+ #
1041
+ def to_s
1042
+ return @labels.join('.')
1043
+ end
1044
+ end
1045
+
1046
+ class Message
1047
+ @@identifier = -1
1048
+
1049
+ def initialize(id = (@@identifier += 1) & 0xffff)
1050
+ @id = id
1051
+ @qr = 0
1052
+ @opcode = 0
1053
+ @aa = 0
1054
+ @tc = 0
1055
+ @rd = 0 # recursion desired
1056
+ @ra = 0 # recursion available
1057
+ @rcode = 0
1058
+ @question = []
1059
+ @answer = []
1060
+ @authority = []
1061
+ @additional = []
1062
+ end
1063
+
1064
+ attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
1065
+ attr_reader :question, :answer, :authority, :additional
1066
+
1067
+ def ==(other)
1068
+ return @id == other.id &&
1069
+ @qr == other.qr &&
1070
+ @opcode == other.opcode &&
1071
+ @aa == other.aa &&
1072
+ @tc == other.tc &&
1073
+ @rd == other.rd &&
1074
+ @ra == other.ra &&
1075
+ @rcode == other.rcode &&
1076
+ @question == other.question &&
1077
+ @answer == other.answer &&
1078
+ @authority == other.authority &&
1079
+ @additional == other.additional
1080
+ end
1081
+
1082
+ def add_question(name, typeclass)
1083
+ @question << [Name.create(name), typeclass]
1084
+ end
1085
+
1086
+ def each_question
1087
+ @question.each {|name, typeclass|
1088
+ yield name, typeclass
1089
+ }
1090
+ end
1091
+
1092
+ def add_answer(name, ttl, data)
1093
+ @answer << [Name.create(name), ttl, data]
1094
+ end
1095
+
1096
+ def each_answer
1097
+ @answer.each {|name, ttl, data|
1098
+ yield name, ttl, data
1099
+ }
1100
+ end
1101
+
1102
+ def add_authority(name, ttl, data)
1103
+ @authority << [Name.create(name), ttl, data]
1104
+ end
1105
+
1106
+ def each_authority
1107
+ @authority.each {|name, ttl, data|
1108
+ yield name, ttl, data
1109
+ }
1110
+ end
1111
+
1112
+ def add_additional(name, ttl, data)
1113
+ @additional << [Name.create(name), ttl, data]
1114
+ end
1115
+
1116
+ def each_additional
1117
+ @additional.each {|name, ttl, data|
1118
+ yield name, ttl, data
1119
+ }
1120
+ end
1121
+
1122
+ def each_resource
1123
+ each_answer {|name, ttl, data| yield name, ttl, data}
1124
+ each_authority {|name, ttl, data| yield name, ttl, data}
1125
+ each_additional {|name, ttl, data| yield name, ttl, data}
1126
+ end
1127
+
1128
+ def encode
1129
+ return MessageEncoder.new {|msg|
1130
+ msg.put_pack('nnnnnn',
1131
+ @id,
1132
+ (@qr & 1) << 15 |
1133
+ (@opcode & 15) << 11 |
1134
+ (@aa & 1) << 10 |
1135
+ (@tc & 1) << 9 |
1136
+ (@rd & 1) << 8 |
1137
+ (@ra & 1) << 7 |
1138
+ (@rcode & 15),
1139
+ @question.length,
1140
+ @answer.length,
1141
+ @authority.length,
1142
+ @additional.length)
1143
+ @question.each {|q|
1144
+ name, typeclass = q
1145
+ msg.put_name(name)
1146
+ msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue)
1147
+ }
1148
+ [@answer, @authority, @additional].each {|rr|
1149
+ rr.each {|r|
1150
+ name, ttl, data = r
1151
+ msg.put_name(name)
1152
+ msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl)
1153
+ msg.put_length16 {data.encode_rdata(msg)}
1154
+ }
1155
+ }
1156
+ }.to_s
1157
+ end
1158
+
1159
+ class MessageEncoder
1160
+ def initialize
1161
+ @data = ''
1162
+ @names = {}
1163
+ yield self
1164
+ end
1165
+
1166
+ def to_s
1167
+ return @data
1168
+ end
1169
+
1170
+ def put_bytes(d)
1171
+ @data << d
1172
+ end
1173
+
1174
+ def put_pack(template, *d)
1175
+ @data << d.pack(template)
1176
+ end
1177
+
1178
+ def put_length16
1179
+ length_index = @data.length
1180
+ @data << "\0\0"
1181
+ data_start = @data.length
1182
+ yield
1183
+ data_end = @data.length
1184
+ @data[length_index, 2] = [data_end - data_start].pack("n")
1185
+ end
1186
+
1187
+ def put_string(d)
1188
+ self.put_pack("C", d.length)
1189
+ @data << d
1190
+ end
1191
+
1192
+ def put_string_list(ds)
1193
+ ds.each {|d|
1194
+ self.put_string(d)
1195
+ }
1196
+ end
1197
+
1198
+ def put_name(d)
1199
+ put_labels(d.to_a)
1200
+ end
1201
+
1202
+ def put_labels(d)
1203
+ d.each_index {|i|
1204
+ domain = d[i..-1]
1205
+ if idx = @names[domain]
1206
+ self.put_pack("n", 0xc000 | idx)
1207
+ return
1208
+ else
1209
+ @names[domain] = @data.length
1210
+ self.put_label(d[i])
1211
+ end
1212
+ }
1213
+ @data << "\0"
1214
+ end
1215
+
1216
+ def put_label(d)
1217
+ self.put_string(d.string)
1218
+ end
1219
+ end
1220
+
1221
+ def Message.decode(m)
1222
+ o = Message.new(0)
1223
+ MessageDecoder.new(m) {|msg|
1224
+ id, flag, qdcount, ancount, nscount, arcount =
1225
+ msg.get_unpack('nnnnnn')
1226
+ o.id = id
1227
+ o.qr = (flag >> 15) & 1
1228
+ o.opcode = (flag >> 11) & 15
1229
+ o.aa = (flag >> 10) & 1
1230
+ o.tc = (flag >> 9) & 1
1231
+ o.rd = (flag >> 8) & 1
1232
+ o.ra = (flag >> 7) & 1
1233
+ o.rcode = flag & 15
1234
+ (1..qdcount).each {
1235
+ name, typeclass = msg.get_question
1236
+ o.add_question(name, typeclass)
1237
+ }
1238
+ (1..ancount).each {
1239
+ name, ttl, data = msg.get_rr
1240
+ o.add_answer(name, ttl, data)
1241
+ }
1242
+ (1..nscount).each {
1243
+ name, ttl, data = msg.get_rr
1244
+ o.add_authority(name, ttl, data)
1245
+ }
1246
+ (1..arcount).each {
1247
+ name, ttl, data = msg.get_rr
1248
+ o.add_additional(name, ttl, data)
1249
+ }
1250
+ }
1251
+ return o
1252
+ end
1253
+
1254
+ class MessageDecoder
1255
+ def initialize(data)
1256
+ @data = data
1257
+ @index = 0
1258
+ @limit = data.length
1259
+ yield self
1260
+ end
1261
+
1262
+ def get_length16
1263
+ len, = self.get_unpack('n')
1264
+ save_limit = @limit
1265
+ @limit = @index + len
1266
+ d = yield(len)
1267
+ if @index < @limit
1268
+ raise DecodeError.new("junk exists")
1269
+ elsif @limit < @index
1270
+ raise DecodeError.new("limit exceeded")
1271
+ end
1272
+ @limit = save_limit
1273
+ return d
1274
+ end
1275
+
1276
+ def get_bytes(len = @limit - @index)
1277
+ d = @data[@index, len]
1278
+ @index += len
1279
+ return d
1280
+ end
1281
+
1282
+ def get_unpack(template)
1283
+ len = 0
1284
+ template.each_byte {|byte|
1285
+ case byte
1286
+ when ?c, ?C
1287
+ len += 1
1288
+ when ?n
1289
+ len += 2
1290
+ when ?N
1291
+ len += 4
1292
+ else
1293
+ raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
1294
+ end
1295
+ }
1296
+ raise DecodeError.new("limit exceeded") if @limit < @index + len
1297
+ arr = @data.unpack("@#{@index}#{template}")
1298
+ @index += len
1299
+ return arr
1300
+ end
1301
+
1302
+ def get_string
1303
+ len = @data[@index]
1304
+ raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len
1305
+ d = @data[@index + 1, len]
1306
+ @index += 1 + len
1307
+ return d
1308
+ end
1309
+
1310
+ def get_string_list
1311
+ strings = []
1312
+ while @index < @limit
1313
+ strings << self.get_string
1314
+ end
1315
+ strings
1316
+ end
1317
+
1318
+ def get_name
1319
+ return Name.new(self.get_labels)
1320
+ end
1321
+
1322
+ def get_labels(limit=nil)
1323
+ limit = @index if !limit || @index < limit
1324
+ d = []
1325
+ while true
1326
+ case @data[@index]
1327
+ when 0
1328
+ @index += 1
1329
+ return d
1330
+ when 192..255
1331
+ idx = self.get_unpack('n')[0] & 0x3fff
1332
+ if limit <= idx
1333
+ raise DecodeError.new("non-backward name pointer")
1334
+ end
1335
+ save_index = @index
1336
+ @index = idx
1337
+ d += self.get_labels(limit)
1338
+ @index = save_index
1339
+ return d
1340
+ else
1341
+ d << self.get_label
1342
+ end
1343
+ end
1344
+ return d
1345
+ end
1346
+
1347
+ def get_label
1348
+ return Label::Str.new(self.get_string)
1349
+ end
1350
+
1351
+ def get_question
1352
+ name = self.get_name
1353
+ type, klass = self.get_unpack("nn")
1354
+ return name, Resource.get_class(type, klass)
1355
+ end
1356
+
1357
+ def get_rr
1358
+ name = self.get_name
1359
+ type, klass, ttl = self.get_unpack('nnN')
1360
+ typeclass = Resource.get_class(type, klass)
1361
+ res = self.get_length16 { typeclass.decode_rdata self }
1362
+ res.instance_variable_set :@ttl, ttl
1363
+ return name, ttl, res
1364
+ end
1365
+ end
1366
+ end
1367
+
1368
+ class Query
1369
+ def encode_rdata(msg)
1370
+ raise EncodeError.new("#{self.class} is query.")
1371
+ end
1372
+
1373
+ def self.decode_rdata(msg)
1374
+ raise DecodeError.new("#{self.class} is query.")
1375
+ end
1376
+ end
1377
+
1378
+ class Resource < Query
1379
+ ClassHash = {}
1380
+
1381
+ attr_reader :ttl
1382
+
1383
+ def encode_rdata(msg)
1384
+ raise NotImplementedError.new
1385
+ end
1386
+
1387
+ def self.decode_rdata(msg)
1388
+ raise NotImplementedError.new
1389
+ end
1390
+
1391
+ def ==(other)
1392
+ return self.class == other.class &&
1393
+ self.instance_variables == other.instance_variables &&
1394
+ self.instance_variables.collect {|name| self.instance_eval name} ==
1395
+ other.instance_variables.collect {|name| other.instance_eval name}
1396
+ end
1397
+
1398
+ def eql?(other)
1399
+ return self == other
1400
+ end
1401
+
1402
+ def hash
1403
+ h = 0
1404
+ self.instance_variables.each {|name|
1405
+ h ^= self.instance_eval("#{name}.hash")
1406
+ }
1407
+ return h
1408
+ end
1409
+
1410
+ def self.get_class(type_value, class_value)
1411
+ return ClassHash[[type_value, class_value]] ||
1412
+ Generic.create(type_value, class_value)
1413
+ end
1414
+
1415
+ class Generic < Resource
1416
+ def initialize(data)
1417
+ @data = data
1418
+ end
1419
+ attr_reader :data
1420
+
1421
+ def encode_rdata(msg)
1422
+ msg.put_bytes(data)
1423
+ end
1424
+
1425
+ def self.decode_rdata(msg)
1426
+ return self.new(msg.get_bytes)
1427
+ end
1428
+
1429
+ def self.create(type_value, class_value)
1430
+ c = Class.new(Generic)
1431
+ c.const_set(:TypeValue, type_value)
1432
+ c.const_set(:ClassValue, class_value)
1433
+ Generic.const_set("Type#{type_value}_Class#{class_value}", c)
1434
+ ClassHash[[type_value, class_value]] = c
1435
+ return c
1436
+ end
1437
+ end
1438
+
1439
+ class DomainName < Resource
1440
+ def initialize(name)
1441
+ @name = name
1442
+ end
1443
+ attr_reader :name
1444
+
1445
+ def encode_rdata(msg)
1446
+ msg.put_name(@name)
1447
+ end
1448
+
1449
+ def self.decode_rdata(msg)
1450
+ return self.new(msg.get_name)
1451
+ end
1452
+ end
1453
+
1454
+ # Standard (class generic) RRs
1455
+ ClassValue = nil
1456
+
1457
+ class NS < DomainName
1458
+ TypeValue = 2
1459
+ end
1460
+
1461
+ class CNAME < DomainName
1462
+ TypeValue = 5
1463
+ end
1464
+
1465
+ class SOA < Resource
1466
+ TypeValue = 6
1467
+
1468
+ def initialize(mname, rname, serial, refresh, retry_, expire, minimum)
1469
+ @mname = mname
1470
+ @rname = rname
1471
+ @serial = serial
1472
+ @refresh = refresh
1473
+ @retry = retry_
1474
+ @expire = expire
1475
+ @minimum = minimum
1476
+ end
1477
+ attr_reader :mname, :rname, :serial, :refresh, :retry, :expire, :minimum
1478
+
1479
+ def encode_rdata(msg)
1480
+ msg.put_name(@mname)
1481
+ msg.put_name(@rname)
1482
+ msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum)
1483
+ end
1484
+
1485
+ def self.decode_rdata(msg)
1486
+ mname = msg.get_name
1487
+ rname = msg.get_name
1488
+ serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN')
1489
+ return self.new(
1490
+ mname, rname, serial, refresh, retry_, expire, minimum)
1491
+ end
1492
+ end
1493
+
1494
+ class PTR < DomainName
1495
+ TypeValue = 12
1496
+ end
1497
+
1498
+ class HINFO < Resource
1499
+ TypeValue = 13
1500
+
1501
+ def initialize(cpu, os)
1502
+ @cpu = cpu
1503
+ @os = os
1504
+ end
1505
+ attr_reader :cpu, :os
1506
+
1507
+ def encode_rdata(msg)
1508
+ msg.put_string(@cpu)
1509
+ msg.put_string(@os)
1510
+ end
1511
+
1512
+ def self.decode_rdata(msg)
1513
+ cpu = msg.get_string
1514
+ os = msg.get_string
1515
+ return self.new(cpu, os)
1516
+ end
1517
+ end
1518
+
1519
+ class MINFO < Resource
1520
+ TypeValue = 14
1521
+
1522
+ def initialize(rmailbx, emailbx)
1523
+ @rmailbx = rmailbx
1524
+ @emailbx = emailbx
1525
+ end
1526
+ attr_reader :rmailbx, :emailbx
1527
+
1528
+ def encode_rdata(msg)
1529
+ msg.put_name(@rmailbx)
1530
+ msg.put_name(@emailbx)
1531
+ end
1532
+
1533
+ def self.decode_rdata(msg)
1534
+ rmailbx = msg.get_string
1535
+ emailbx = msg.get_string
1536
+ return self.new(rmailbx, emailbx)
1537
+ end
1538
+ end
1539
+
1540
+ class MX < Resource
1541
+ TypeValue= 15
1542
+
1543
+ def initialize(preference, exchange)
1544
+ @preference = preference
1545
+ @exchange = exchange
1546
+ end
1547
+ attr_reader :preference, :exchange
1548
+
1549
+ def encode_rdata(msg)
1550
+ msg.put_pack('n', @preference)
1551
+ msg.put_name(@exchange)
1552
+ end
1553
+
1554
+ def self.decode_rdata(msg)
1555
+ preference, = msg.get_unpack('n')
1556
+ exchange = msg.get_name
1557
+ return self.new(preference, exchange)
1558
+ end
1559
+ end
1560
+
1561
+ class TXT < Resource
1562
+ TypeValue = 16
1563
+
1564
+ def initialize(first_string, *rest_strings)
1565
+ @strings = [first_string, *rest_strings]
1566
+ end
1567
+ attr_reader :strings
1568
+
1569
+ def data
1570
+ @strings[0]
1571
+ end
1572
+
1573
+ def encode_rdata(msg)
1574
+ msg.put_string_list(@strings)
1575
+ end
1576
+
1577
+ def self.decode_rdata(msg)
1578
+ strings = msg.get_string_list
1579
+ return self.new(*strings)
1580
+ end
1581
+ end
1582
+
1583
+ class ANY < Query
1584
+ TypeValue = 255
1585
+ end
1586
+
1587
+ ClassInsensitiveTypes = [
1588
+ NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, ANY
1589
+ ]
1590
+
1591
+ # ARPA Internet specific RRs
1592
+ module IN
1593
+ ClassValue = 1
1594
+
1595
+ ClassInsensitiveTypes.each {|s|
1596
+ c = Class.new(s)
1597
+ c.const_set(:TypeValue, s::TypeValue)
1598
+ c.const_set(:ClassValue, ClassValue)
1599
+ ClassHash[[s::TypeValue, ClassValue]] = c
1600
+ self.const_set(s.name.sub(/.*::/, ''), c)
1601
+ }
1602
+
1603
+ class A < Resource
1604
+ ClassHash[[TypeValue = 1, ClassValue = ClassValue]] = self
1605
+
1606
+ def initialize(address)
1607
+ @address = IPv4.create(address)
1608
+ end
1609
+ attr_reader :address
1610
+
1611
+ def encode_rdata(msg)
1612
+ msg.put_bytes(@address.address)
1613
+ end
1614
+
1615
+ def self.decode_rdata(msg)
1616
+ return self.new(IPv4.new(msg.get_bytes(4)))
1617
+ end
1618
+ end
1619
+
1620
+ class WKS < Resource
1621
+ ClassHash[[TypeValue = 11, ClassValue = ClassValue]] = self
1622
+
1623
+ def initialize(address, protocol, bitmap)
1624
+ @address = IPv4.create(address)
1625
+ @protocol = protocol
1626
+ @bitmap = bitmap
1627
+ end
1628
+ attr_reader :address, :protocol, :bitmap
1629
+
1630
+ def encode_rdata(msg)
1631
+ msg.put_bytes(@address.address)
1632
+ msg.put_pack("n", @protocol)
1633
+ msg.put_bytes(@bitmap)
1634
+ end
1635
+
1636
+ def self.decode_rdata(msg)
1637
+ address = IPv4.new(msg.get_bytes(4))
1638
+ protocol, = msg.get_unpack("n")
1639
+ bitmap = msg.get_bytes
1640
+ return self.new(address, protocol, bitmap)
1641
+ end
1642
+ end
1643
+
1644
+ class AAAA < Resource
1645
+ ClassHash[[TypeValue = 28, ClassValue = ClassValue]] = self
1646
+
1647
+ def initialize(address)
1648
+ @address = IPv6.create(address)
1649
+ end
1650
+ attr_reader :address
1651
+
1652
+ def encode_rdata(msg)
1653
+ msg.put_bytes(@address.address)
1654
+ end
1655
+
1656
+ def self.decode_rdata(msg)
1657
+ return self.new(IPv6.new(msg.get_bytes(16)))
1658
+ end
1659
+ end
1660
+
1661
+ # SRV resource record defined in RFC 2782
1662
+ #
1663
+ # These records identify the hostname and port that a service is
1664
+ # available at.
1665
+ #
1666
+ # The format is:
1667
+ # _Service._Proto.Name TTL Class SRV Priority Weight Port Target
1668
+ #
1669
+ # The fields specific to SRV are defined in RFC 2782 as meaning:
1670
+ # - +priority+ The priority of this target host. A client MUST attempt
1671
+ # to contact the target host with the lowest-numbered priority it can
1672
+ # reach; target hosts with the same priority SHOULD be tried in an
1673
+ # order defined by the weight field. The range is 0-65535. Note that
1674
+ # it is not widely implemented and should be set to zero.
1675
+ #
1676
+ # - +weight+ A server selection mechanism. The weight field specifies
1677
+ # a relative weight for entries with the same priority. Larger weights
1678
+ # SHOULD be given a proportionately higher probability of being
1679
+ # selected. The range of this number is 0-65535. Domain administrators
1680
+ # SHOULD use Weight 0 when there isn't any server selection to do, to
1681
+ # make the RR easier to read for humans (less noisy). Note that it is
1682
+ # not widely implemented and should be set to zero.
1683
+ #
1684
+ # - +port+ The port on this target host of this service. The range is 0-
1685
+ # 65535.
1686
+ #
1687
+ # - +target+ The domain name of the target host. A target of "." means
1688
+ # that the service is decidedly not available at this domain.
1689
+ class SRV < Resource
1690
+ ClassHash[[TypeValue = 33, ClassValue = ClassValue]] = self
1691
+
1692
+ # Create a SRV resource record.
1693
+ def initialize(priority, weight, port, target)
1694
+ @priority = priority.to_int
1695
+ @weight = weight.to_int
1696
+ @port = port.to_int
1697
+ @target = Name.create(target)
1698
+ end
1699
+
1700
+ attr_reader :priority, :weight, :port, :target
1701
+
1702
+ def encode_rdata(msg)
1703
+ msg.put_pack("n", @priority)
1704
+ msg.put_pack("n", @weight)
1705
+ msg.put_pack("n", @port)
1706
+ msg.put_name(@target)
1707
+ end
1708
+
1709
+ def self.decode_rdata(msg)
1710
+ priority, = msg.get_unpack("n")
1711
+ weight, = msg.get_unpack("n")
1712
+ port, = msg.get_unpack("n")
1713
+ target = msg.get_name
1714
+ return self.new(priority, weight, port, target)
1715
+ end
1716
+ end
1717
+
1718
+ end
1719
+ end
1720
+ end
1721
+
1722
+ class IPv4
1723
+ Regex = /\A(\d+)\.(\d+)\.(\d+)\.(\d+)\z/
1724
+
1725
+ def self.create(arg)
1726
+ case arg
1727
+ when IPv4
1728
+ return arg
1729
+ when Regex
1730
+ if (0..255) === (a = $1.to_i) &&
1731
+ (0..255) === (b = $2.to_i) &&
1732
+ (0..255) === (c = $3.to_i) &&
1733
+ (0..255) === (d = $4.to_i)
1734
+ return self.new([a, b, c, d].pack("CCCC"))
1735
+ else
1736
+ raise ArgumentError.new("IPv4 address with invalid value: " + arg)
1737
+ end
1738
+ else
1739
+ raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}")
1740
+ end
1741
+ end
1742
+
1743
+ def initialize(address)
1744
+ unless address.kind_of?(String) && address.length == 4
1745
+ raise ArgumentError.new('IPv4 address must be 4 bytes')
1746
+ end
1747
+ @address = address
1748
+ end
1749
+ attr_reader :address
1750
+
1751
+ def to_s
1752
+ return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
1753
+ end
1754
+
1755
+ def inspect
1756
+ return "#<#{self.class} #{self.to_s}>"
1757
+ end
1758
+
1759
+ def to_name
1760
+ return DNS::Name.create(
1761
+ '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
1762
+ end
1763
+
1764
+ def ==(other)
1765
+ return @address == other.address
1766
+ end
1767
+
1768
+ def eql?(other)
1769
+ return self == other
1770
+ end
1771
+
1772
+ def hash
1773
+ return @address.hash
1774
+ end
1775
+ end
1776
+
1777
+ class IPv6
1778
+ Regex_8Hex = /\A
1779
+ (?:[0-9A-Fa-f]{1,4}:){7}
1780
+ [0-9A-Fa-f]{1,4}
1781
+ \z/x
1782
+
1783
+ Regex_CompressedHex = /\A
1784
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
1785
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
1786
+ \z/x
1787
+
1788
+ Regex_6Hex4Dec = /\A
1789
+ ((?:[0-9A-Fa-f]{1,4}:){6,6})
1790
+ (\d+)\.(\d+)\.(\d+)\.(\d+)
1791
+ \z/x
1792
+
1793
+ Regex_CompressedHex4Dec = /\A
1794
+ ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
1795
+ ((?:[0-9A-Fa-f]{1,4}:)*)
1796
+ (\d+)\.(\d+)\.(\d+)\.(\d+)
1797
+ \z/x
1798
+
1799
+ Regex = /
1800
+ (?:#{Regex_8Hex}) |
1801
+ (?:#{Regex_CompressedHex}) |
1802
+ (?:#{Regex_6Hex4Dec}) |
1803
+ (?:#{Regex_CompressedHex4Dec})/x
1804
+
1805
+ def self.create(arg)
1806
+ case arg
1807
+ when IPv6
1808
+ return arg
1809
+ when String
1810
+ address = ''
1811
+ if Regex_8Hex =~ arg
1812
+ arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
1813
+ elsif Regex_CompressedHex =~ arg
1814
+ prefix = $1
1815
+ suffix = $2
1816
+ a1 = ''
1817
+ a2 = ''
1818
+ prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
1819
+ suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
1820
+ omitlen = 16 - a1.length - a2.length
1821
+ address << a1 << "\0" * omitlen << a2
1822
+ elsif Regex_6Hex4Dec =~ arg
1823
+ prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
1824
+ if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
1825
+ prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
1826
+ address << [a, b, c, d].pack('CCCC')
1827
+ else
1828
+ raise ArgumentError.new("not numeric IPv6 address: " + arg)
1829
+ end
1830
+ elsif Regex_CompressedHex4Dec =~ arg
1831
+ prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i
1832
+ if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
1833
+ a1 = ''
1834
+ a2 = ''
1835
+ prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
1836
+ suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
1837
+ omitlen = 12 - a1.length - a2.length
1838
+ address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
1839
+ else
1840
+ raise ArgumentError.new("not numeric IPv6 address: " + arg)
1841
+ end
1842
+ else
1843
+ raise ArgumentError.new("not numeric IPv6 address: " + arg)
1844
+ end
1845
+ return IPv6.new(address)
1846
+ else
1847
+ raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}")
1848
+ end
1849
+ end
1850
+
1851
+ def initialize(address)
1852
+ unless address.kind_of?(String) && address.length == 16
1853
+ raise ArgumentError.new('IPv6 address must be 16 bytes')
1854
+ end
1855
+ @address = address
1856
+ end
1857
+ attr_reader :address
1858
+
1859
+ def to_s
1860
+ address = sprintf("%X:%X:%X:%X:%X:%X:%X:%X", *@address.unpack("nnnnnnnn"))
1861
+ unless address.sub!(/(^|:)0(:0)+(:|$)/, '::')
1862
+ address.sub!(/(^|:)0(:|$)/, '::')
1863
+ end
1864
+ return address
1865
+ end
1866
+
1867
+ def inspect
1868
+ return "#<#{self.class} #{self.to_s}>"
1869
+ end
1870
+
1871
+ def to_name
1872
+ # ip6.arpa should be searched too. [RFC3152]
1873
+ return DNS::Name.new(
1874
+ @address.unpack("H32")[0].split(//).reverse + ['ip6', 'int'])
1875
+ end
1876
+
1877
+ def ==(other)
1878
+ return @address == other.address
1879
+ end
1880
+
1881
+ def eql?(other)
1882
+ return self == other
1883
+ end
1884
+
1885
+ def hash
1886
+ return @address.hash
1887
+ end
1888
+ end
1889
+
1890
+ DefaultResolver = self.new
1891
+ AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/
1892
+ end