logmerge 1.0.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.
@@ -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