1require 'socket'
2require 'fcntl'
3require 'timeout'
4require 'thread'
5
6begin
7  require 'securerandom'
8rescue LoadError
9end
10
11# Resolv is a thread-aware DNS resolver library written in Ruby.  Resolv can
12# handle multiple DNS requests concurrently without blocking the entire ruby
13# interpreter.
14#
15# See also resolv-replace.rb to replace the libc resolver with Resolv.
16#
17# Resolv can look up various DNS resources using the DNS module directly.
18#
19# Examples:
20#
21#   p Resolv.getaddress "www.ruby-lang.org"
22#   p Resolv.getname "210.251.121.214"
23#
24#   Resolv::DNS.open do |dns|
25#     ress = dns.getresources "www.ruby-lang.org", Resolv::DNS::Resource::IN::A
26#     p ress.map { |r| r.address }
27#     ress = dns.getresources "ruby-lang.org", Resolv::DNS::Resource::IN::MX
28#     p ress.map { |r| [r.exchange.to_s, r.preference] }
29#   end
30#
31#
32# == Bugs
33#
34# * NIS is not supported.
35# * /etc/nsswitch.conf is not supported.
36
37class Resolv
38
39  ##
40  # Looks up the first IP address for +name+.
41
42  def self.getaddress(name)
43    DefaultResolver.getaddress(name)
44  end
45
46  ##
47  # Looks up all IP address for +name+.
48
49  def self.getaddresses(name)
50    DefaultResolver.getaddresses(name)
51  end
52
53  ##
54  # Iterates over all IP addresses for +name+.
55
56  def self.each_address(name, &block)
57    DefaultResolver.each_address(name, &block)
58  end
59
60  ##
61  # Looks up the hostname of +address+.
62
63  def self.getname(address)
64    DefaultResolver.getname(address)
65  end
66
67  ##
68  # Looks up all hostnames for +address+.
69
70  def self.getnames(address)
71    DefaultResolver.getnames(address)
72  end
73
74  ##
75  # Iterates over all hostnames for +address+.
76
77  def self.each_name(address, &proc)
78    DefaultResolver.each_name(address, &proc)
79  end
80
81  ##
82  # Creates a new Resolv using +resolvers+.
83
84  def initialize(resolvers=[Hosts.new, DNS.new])
85    @resolvers = resolvers
86  end
87
88  ##
89  # Looks up the first IP address for +name+.
90
91  def getaddress(name)
92    each_address(name) {|address| return address}
93    raise ResolvError.new("no address for #{name}")
94  end
95
96  ##
97  # Looks up all IP address for +name+.
98
99  def getaddresses(name)
100    ret = []
101    each_address(name) {|address| ret << address}
102    return ret
103  end
104
105  ##
106  # Iterates over all IP addresses for +name+.
107
108  def each_address(name)
109    if AddressRegex =~ name
110      yield name
111      return
112    end
113    yielded = false
114    @resolvers.each {|r|
115      r.each_address(name) {|address|
116        yield address.to_s
117        yielded = true
118      }
119      return if yielded
120    }
121  end
122
123  ##
124  # Looks up the hostname of +address+.
125
126  def getname(address)
127    each_name(address) {|name| return name}
128    raise ResolvError.new("no name for #{address}")
129  end
130
131  ##
132  # Looks up all hostnames for +address+.
133
134  def getnames(address)
135    ret = []
136    each_name(address) {|name| ret << name}
137    return ret
138  end
139
140  ##
141  # Iterates over all hostnames for +address+.
142
143  def each_name(address)
144    yielded = false
145    @resolvers.each {|r|
146      r.each_name(address) {|name|
147        yield name.to_s
148        yielded = true
149      }
150      return if yielded
151    }
152  end
153
154  ##
155  # Indicates a failure to resolve a name or address.
156
157  class ResolvError < StandardError; end
158
159  ##
160  # Indicates a timeout resolving a name or address.
161
162  class ResolvTimeout < TimeoutError; end
163
164  ##
165  # Resolv::Hosts is a hostname resolver that uses the system hosts file.
166
167  class Hosts
168    if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM
169      require 'win32/resolv'
170      DefaultFileName = Win32::Resolv.get_hosts_path
171    else
172      DefaultFileName = '/etc/hosts'
173    end
174
175    ##
176    # Creates a new Resolv::Hosts, using +filename+ for its data source.
177
178    def initialize(filename = DefaultFileName)
179      @filename = filename
180      @mutex = Mutex.new
181      @initialized = nil
182    end
183
184    def lazy_initialize # :nodoc:
185      @mutex.synchronize {
186        unless @initialized
187          @name2addr = {}
188          @addr2name = {}
189          open(@filename, 'rb') {|f|
190            f.each {|line|
191              line.sub!(/#.*/, '')
192              addr, hostname, *aliases = line.split(/\s+/)
193              next unless addr
194              addr.untaint
195              hostname.untaint
196              @addr2name[addr] = [] unless @addr2name.include? addr
197              @addr2name[addr] << hostname
198              @addr2name[addr] += aliases
199              @name2addr[hostname] = [] unless @name2addr.include? hostname
200              @name2addr[hostname] << addr
201              aliases.each {|n|
202                n.untaint
203                @name2addr[n] = [] unless @name2addr.include? n
204                @name2addr[n] << addr
205              }
206            }
207          }
208          @name2addr.each {|name, arr| arr.reverse!}
209          @initialized = true
210        end
211      }
212      self
213    end
214
215    ##
216    # Gets the IP address of +name+ from the hosts file.
217
218    def getaddress(name)
219      each_address(name) {|address| return address}
220      raise ResolvError.new("#{@filename} has no name: #{name}")
221    end
222
223    ##
224    # Gets all IP addresses for +name+ from the hosts file.
225
226    def getaddresses(name)
227      ret = []
228      each_address(name) {|address| ret << address}
229      return ret
230    end
231
232    ##
233    # Iterates over all IP addresses for +name+ retrieved from the hosts file.
234
235    def each_address(name, &proc)
236      lazy_initialize
237      if @name2addr.include?(name)
238        @name2addr[name].each(&proc)
239      end
240    end
241
242    ##
243    # Gets the hostname of +address+ from the hosts file.
244
245    def getname(address)
246      each_name(address) {|name| return name}
247      raise ResolvError.new("#{@filename} has no address: #{address}")
248    end
249
250    ##
251    # Gets all hostnames for +address+ from the hosts file.
252
253    def getnames(address)
254      ret = []
255      each_name(address) {|name| ret << name}
256      return ret
257    end
258
259    ##
260    # Iterates over all hostnames for +address+ retrieved from the hosts file.
261
262    def each_name(address, &proc)
263      lazy_initialize
264      if @addr2name.include?(address)
265        @addr2name[address].each(&proc)
266      end
267    end
268  end
269
270  ##
271  # Resolv::DNS is a DNS stub resolver.
272  #
273  # Information taken from the following places:
274  #
275  # * STD0013
276  # * RFC 1035
277  # * ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
278  # * etc.
279
280  class DNS
281
282    ##
283    # Default DNS Port
284
285    Port = 53
286
287    ##
288    # Default DNS UDP packet size
289
290    UDPSize = 512
291
292    ##
293    # Creates a new DNS resolver.  See Resolv::DNS.new for argument details.
294    #
295    # Yields the created DNS resolver to the block, if given, otherwise
296    # returns it.
297
298    def self.open(*args)
299      dns = new(*args)
300      return dns unless block_given?
301      begin
302        yield dns
303      ensure
304        dns.close
305      end
306    end
307
308    ##
309    # Creates a new DNS resolver.
310    #
311    # +config_info+ can be:
312    #
313    # nil:: Uses /etc/resolv.conf.
314    # String:: Path to a file using /etc/resolv.conf's format.
315    # Hash:: Must contain :nameserver, :search and :ndots keys.
316    # :nameserver_port can be used to specify port number of nameserver address.
317    #
318    # The value of :nameserver should be an address string or
319    # an array of address strings.
320    # - :nameserver => '8.8.8.8'
321    # - :nameserver => ['8.8.8.8', '8.8.4.4']
322    #
323    # The value of :nameserver_port should be an array of
324    # pair of nameserver address and port number.
325    # - :nameserver_port => [['8.8.8.8', 53], ['8.8.4.4', 53]]
326    #
327    # Example:
328    #
329    #   Resolv::DNS.new(:nameserver => ['210.251.121.21'],
330    #                   :search => ['ruby-lang.org'],
331    #                   :ndots => 1)
332
333    def initialize(config_info=nil)
334      @mutex = Mutex.new
335      @config = Config.new(config_info)
336      @initialized = nil
337    end
338
339    # Sets the resolver timeouts.  This may be a single positive number
340    # or an array of positive numbers representing timeouts in seconds.
341    # If an array is specified, a DNS request will retry and wait for
342    # each successive interval in the array until a successful response
343    # is received.  Specifying +nil+ reverts to the default timeouts:
344    # [ 5, second = 5 * 2 / nameserver_count, 2 * second, 4 * second ]
345    #
346    # Example:
347    #
348    #   dns.timeouts = 3
349    #
350    def timeouts=(values)
351      @config.timeouts = values
352    end
353
354    def lazy_initialize # :nodoc:
355      @mutex.synchronize {
356        unless @initialized
357          @config.lazy_initialize
358          @initialized = true
359        end
360      }
361      self
362    end
363
364    ##
365    # Closes the DNS resolver.
366
367    def close
368      @mutex.synchronize {
369        if @initialized
370          @initialized = false
371        end
372      }
373    end
374
375    ##
376    # Gets the IP address of +name+ from the DNS resolver.
377    #
378    # +name+ can be a Resolv::DNS::Name or a String.  Retrieved address will
379    # be a Resolv::IPv4 or Resolv::IPv6
380
381    def getaddress(name)
382      each_address(name) {|address| return address}
383      raise ResolvError.new("DNS result has no information for #{name}")
384    end
385
386    ##
387    # Gets all IP addresses for +name+ from the DNS resolver.
388    #
389    # +name+ can be a Resolv::DNS::Name or a String.  Retrieved addresses will
390    # be a Resolv::IPv4 or Resolv::IPv6
391
392    def getaddresses(name)
393      ret = []
394      each_address(name) {|address| ret << address}
395      return ret
396    end
397
398    ##
399    # Iterates over all IP addresses for +name+ retrieved from the DNS
400    # resolver.
401    #
402    # +name+ can be a Resolv::DNS::Name or a String.  Retrieved addresses will
403    # be a Resolv::IPv4 or Resolv::IPv6
404
405    def each_address(name)
406      each_resource(name, Resource::IN::A) {|resource| yield resource.address}
407      if use_ipv6?
408        each_resource(name, Resource::IN::AAAA) {|resource| yield resource.address}
409      end
410    end
411
412    def use_ipv6? # :nodoc:
413      begin
414        list = Socket.ip_address_list
415      rescue NotImplementedError
416        return true
417      end
418      list.any? {|a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? }
419    end
420    private :use_ipv6?
421
422    ##
423    # Gets the hostname for +address+ from the DNS resolver.
424    #
425    # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String.  Retrieved
426    # name will be a Resolv::DNS::Name.
427
428    def getname(address)
429      each_name(address) {|name| return name}
430      raise ResolvError.new("DNS result has no information for #{address}")
431    end
432
433    ##
434    # Gets all hostnames for +address+ from the DNS resolver.
435    #
436    # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String.  Retrieved
437    # names will be Resolv::DNS::Name instances.
438
439    def getnames(address)
440      ret = []
441      each_name(address) {|name| ret << name}
442      return ret
443    end
444
445    ##
446    # Iterates over all hostnames for +address+ retrieved from the DNS
447    # resolver.
448    #
449    # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String.  Retrieved
450    # names will be Resolv::DNS::Name instances.
451
452    def each_name(address)
453      case address
454      when Name
455        ptr = address
456      when IPv4::Regex
457        ptr = IPv4.create(address).to_name
458      when IPv6::Regex
459        ptr = IPv6.create(address).to_name
460      else
461        raise ResolvError.new("cannot interpret as address: #{address}")
462      end
463      each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name}
464    end
465
466    ##
467    # Look up the +typeclass+ DNS resource of +name+.
468    #
469    # +name+ must be a Resolv::DNS::Name or a String.
470    #
471    # +typeclass+ should be one of the following:
472    #
473    # * Resolv::DNS::Resource::IN::A
474    # * Resolv::DNS::Resource::IN::AAAA
475    # * Resolv::DNS::Resource::IN::ANY
476    # * Resolv::DNS::Resource::IN::CNAME
477    # * Resolv::DNS::Resource::IN::HINFO
478    # * Resolv::DNS::Resource::IN::MINFO
479    # * Resolv::DNS::Resource::IN::MX
480    # * Resolv::DNS::Resource::IN::NS
481    # * Resolv::DNS::Resource::IN::PTR
482    # * Resolv::DNS::Resource::IN::SOA
483    # * Resolv::DNS::Resource::IN::TXT
484    # * Resolv::DNS::Resource::IN::WKS
485    #
486    # Returned resource is represented as a Resolv::DNS::Resource instance,
487    # i.e. Resolv::DNS::Resource::IN::A.
488
489    def getresource(name, typeclass)
490      each_resource(name, typeclass) {|resource| return resource}
491      raise ResolvError.new("DNS result has no information for #{name}")
492    end
493
494    ##
495    # Looks up all +typeclass+ DNS resources for +name+.  See #getresource for
496    # argument details.
497
498    def getresources(name, typeclass)
499      ret = []
500      each_resource(name, typeclass) {|resource| ret << resource}
501      return ret
502    end
503
504    ##
505    # Iterates over all +typeclass+ DNS resources for +name+.  See
506    # #getresource for argument details.
507
508    def each_resource(name, typeclass, &proc)
509      lazy_initialize
510      requester = make_udp_requester
511      senders = {}
512      begin
513        @config.resolv(name) {|candidate, tout, nameserver, port|
514          msg = Message.new
515          msg.rd = 1
516          msg.add_question(candidate, typeclass)
517          unless sender = senders[[candidate, nameserver, port]]
518            sender = requester.sender(msg, candidate, nameserver, port)
519            next if !sender
520            senders[[candidate, nameserver, port]] = sender
521          end
522          reply, reply_name = requester.request(sender, tout)
523          case reply.rcode
524          when RCode::NoError
525            if reply.tc == 1 and not Requester::TCP === requester
526              requester.close
527              # Retry via TCP:
528              requester = make_tcp_requester(nameserver, port)
529              senders = {}
530              # This will use TCP for all remaining candidates (assuming the
531              # current candidate does not already respond successfully via
532              # TCP).  This makes sense because we already know the full
533              # response will not fit in an untruncated UDP packet.
534              redo
535            else
536              extract_resources(reply, reply_name, typeclass, &proc)
537            end
538            return
539          when RCode::NXDomain
540            raise Config::NXDomain.new(reply_name.to_s)
541          else
542            raise Config::OtherResolvError.new(reply_name.to_s)
543          end
544        }
545      ensure
546        requester.close
547      end
548    end
549
550    def make_udp_requester # :nodoc:
551      nameserver_port = @config.nameserver_port
552      if nameserver_port.length == 1
553        Requester::ConnectedUDP.new(*nameserver_port[0])
554      else
555        Requester::UnconnectedUDP.new(*nameserver_port)
556      end
557    end
558
559    def make_tcp_requester(host, port) # :nodoc:
560      return Requester::TCP.new(host, port)
561    end
562
563    def extract_resources(msg, name, typeclass) # :nodoc:
564      if typeclass < Resource::ANY
565        n0 = Name.create(name)
566        msg.each_answer {|n, ttl, data|
567          yield data if n0 == n
568        }
569      end
570      yielded = false
571      n0 = Name.create(name)
572      msg.each_answer {|n, ttl, data|
573        if n0 == n
574          case data
575          when typeclass
576            yield data
577            yielded = true
578          when Resource::CNAME
579            n0 = data.name
580          end
581        end
582      }
583      return if yielded
584      msg.each_answer {|n, ttl, data|
585        if n0 == n
586          case data
587          when typeclass
588            yield data
589          end
590        end
591      }
592    end
593
594    if defined? SecureRandom
595      def self.random(arg) # :nodoc:
596        begin
597          SecureRandom.random_number(arg)
598        rescue NotImplementedError
599          rand(arg)
600        end
601      end
602    else
603      def self.random(arg) # :nodoc:
604        rand(arg)
605      end
606    end
607
608
609    def self.rangerand(range) # :nodoc:
610      base = range.begin
611      len = range.end - range.begin
612      if !range.exclude_end?
613        len += 1
614      end
615      base + random(len)
616    end
617
618    RequestID = {} # :nodoc:
619    RequestIDMutex = Mutex.new # :nodoc:
620
621    def self.allocate_request_id(host, port) # :nodoc:
622      id = nil
623      RequestIDMutex.synchronize {
624        h = (RequestID[[host, port]] ||= {})
625        begin
626          id = rangerand(0x0000..0xffff)
627        end while h[id]
628        h[id] = true
629      }
630      id
631    end
632
633    def self.free_request_id(host, port, id) # :nodoc:
634      RequestIDMutex.synchronize {
635        key = [host, port]
636        if h = RequestID[key]
637          h.delete id
638          if h.empty?
639            RequestID.delete key
640          end
641        end
642      }
643    end
644
645    def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc:
646      begin
647        port = rangerand(1024..65535)
648        udpsock.bind(bind_host, port)
649      rescue Errno::EADDRINUSE
650        retry
651      end
652    end
653
654    class Requester # :nodoc:
655      def initialize
656        @senders = {}
657        @socks = nil
658      end
659
660      def request(sender, tout)
661        start = Time.now
662        timelimit = start + tout
663        sender.send
664        while true
665          before_select = Time.now
666          timeout = timelimit - before_select
667          if timeout <= 0
668            raise ResolvTimeout
669          end
670          select_result = IO.select(@socks, nil, nil, timeout)
671          if !select_result
672            after_select = Time.now
673            next if after_select < timelimit
674            raise ResolvTimeout
675          end
676          begin
677            reply, from = recv_reply(select_result[0])
678          rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
679                 Errno::ECONNRESET # Windows
680            # No name server running on the server?
681            # Don't wait anymore.
682            raise ResolvTimeout
683          end
684          begin
685            msg = Message.decode(reply)
686          rescue DecodeError
687            next # broken DNS message ignored
688          end
689          if s = @senders[[from,msg.id]]
690            break
691          else
692            # unexpected DNS message ignored
693          end
694        end
695        return msg, s.data
696      end
697
698      def close
699        socks = @socks
700        @socks = nil
701        if socks
702          socks.each {|sock| sock.close }
703        end
704      end
705
706      class Sender # :nodoc:
707        def initialize(msg, data, sock)
708          @msg = msg
709          @data = data
710          @sock = sock
711        end
712      end
713
714      class UnconnectedUDP < Requester # :nodoc:
715        def initialize(*nameserver_port)
716          super()
717          @nameserver_port = nameserver_port
718          @socks_hash = {}
719          @socks = []
720          nameserver_port.each {|host, port|
721            if host.index(':')
722              bind_host = "::"
723              af = Socket::AF_INET6
724            else
725              bind_host = "0.0.0.0"
726              af = Socket::AF_INET
727            end
728            next if @socks_hash[bind_host]
729            begin
730              sock = UDPSocket.new(af)
731            rescue Errno::EAFNOSUPPORT
732              next # The kernel doesn't support the address family.
733            end
734            sock.do_not_reverse_lookup = true
735            sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::F_SETFD
736            DNS.bind_random_port(sock, bind_host)
737            @socks << sock
738            @socks_hash[bind_host] = sock
739          }
740        end
741
742        def recv_reply(readable_socks)
743          reply, from = readable_socks[0].recvfrom(UDPSize)
744          return reply, [from[3],from[1]]
745        end
746
747        def sender(msg, data, host, port=Port)
748          sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"]
749          return nil if !sock
750          service = [host, port]
751          id = DNS.allocate_request_id(host, port)
752          request = msg.encode
753          request[0,2] = [id].pack('n')
754          return @senders[[service, id]] =
755            Sender.new(request, data, sock, host, port)
756        end
757
758        def close
759          super
760          @senders.each_key {|service, id|
761            DNS.free_request_id(service[0], service[1], id)
762          }
763        end
764
765        class Sender < Requester::Sender # :nodoc:
766          def initialize(msg, data, sock, host, port)
767            super(msg, data, sock)
768            @host = host
769            @port = port
770          end
771          attr_reader :data
772
773          def send
774            raise "@sock is nil." if @sock.nil?
775            @sock.send(@msg, 0, @host, @port)
776          end
777        end
778      end
779
780      class ConnectedUDP < Requester # :nodoc:
781        def initialize(host, port=Port)
782          super()
783          @host = host
784          @port = port
785          is_ipv6 = host.index(':')
786          sock = UDPSocket.new(is_ipv6 ? Socket::AF_INET6 : Socket::AF_INET)
787          @socks = [sock]
788          sock.do_not_reverse_lookup = true
789          sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::F_SETFD
790          DNS.bind_random_port(sock, is_ipv6 ? "::" : "0.0.0.0")
791          sock.connect(host, port)
792        end
793
794        def recv_reply(readable_socks)
795          reply = readable_socks[0].recv(UDPSize)
796          return reply, nil
797        end
798
799        def sender(msg, data, host=@host, port=@port)
800          unless host == @host && port == @port
801            raise RequestError.new("host/port don't match: #{host}:#{port}")
802          end
803          id = DNS.allocate_request_id(@host, @port)
804          request = msg.encode
805          request[0,2] = [id].pack('n')
806          return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
807        end
808
809        def close
810          super
811          @senders.each_key {|from, id|
812            DNS.free_request_id(@host, @port, id)
813          }
814        end
815
816        class Sender < Requester::Sender # :nodoc:
817          def send
818            raise "@sock is nil." if @sock.nil?
819            @sock.send(@msg, 0)
820          end
821          attr_reader :data
822        end
823      end
824
825      class TCP < Requester # :nodoc:
826        def initialize(host, port=Port)
827          super()
828          @host = host
829          @port = port
830          sock = TCPSocket.new(@host, @port)
831          @socks = [sock]
832          sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::F_SETFD
833          @senders = {}
834        end
835
836        def recv_reply(readable_socks)
837          len = readable_socks[0].read(2).unpack('n')[0]
838          reply = @socks[0].read(len)
839          return reply, nil
840        end
841
842        def sender(msg, data, host=@host, port=@port)
843          unless host == @host && port == @port
844            raise RequestError.new("host/port don't match: #{host}:#{port}")
845          end
846          id = DNS.allocate_request_id(@host, @port)
847          request = msg.encode
848          request[0,2] = [request.length, id].pack('nn')
849          return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
850        end
851
852        class Sender < Requester::Sender # :nodoc:
853          def send
854            @sock.print(@msg)
855            @sock.flush
856          end
857          attr_reader :data
858        end
859
860        def close
861          super
862          @senders.each_key {|from,id|
863            DNS.free_request_id(@host, @port, id)
864          }
865        end
866      end
867
868      ##
869      # Indicates a problem with the DNS request.
870
871      class RequestError < StandardError
872      end
873    end
874
875    class Config # :nodoc:
876      def initialize(config_info=nil)
877        @mutex = Mutex.new
878        @config_info = config_info
879        @initialized = nil
880        @timeouts = nil
881      end
882
883      def timeouts=(values)
884        if values
885          values = Array(values)
886          values.each do |t|
887            Numeric === t or raise ArgumentError, "#{t.inspect} is not numeric"
888            t > 0.0 or raise ArgumentError, "timeout=#{t} must be postive"
889          end
890          @timeouts = values
891        else
892          @timeouts = nil
893        end
894      end
895
896      def Config.parse_resolv_conf(filename)
897        nameserver = []
898        search = nil
899        ndots = 1
900        open(filename, 'rb') {|f|
901          f.each {|line|
902            line.sub!(/[#;].*/, '')
903            keyword, *args = line.split(/\s+/)
904            args.each { |arg|
905              arg.untaint
906            }
907            next unless keyword
908            case keyword
909            when 'nameserver'
910              nameserver += args
911            when 'domain'
912              next if args.empty?
913              search = [args[0]]
914            when 'search'
915              next if args.empty?
916              search = args
917            when 'options'
918              args.each {|arg|
919                case arg
920                when /\Andots:(\d+)\z/
921                  ndots = $1.to_i
922                end
923              }
924            end
925          }
926        }
927        return { :nameserver => nameserver, :search => search, :ndots => ndots }
928      end
929
930      def Config.default_config_hash(filename="/etc/resolv.conf")
931        if File.exist? filename
932          config_hash = Config.parse_resolv_conf(filename)
933        else
934          if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
935            require 'win32/resolv'
936            search, nameserver = Win32::Resolv.get_resolv_info
937            config_hash = {}
938            config_hash[:nameserver] = nameserver if nameserver
939            config_hash[:search] = [search].flatten if search
940          end
941        end
942        config_hash || {}
943      end
944
945      def lazy_initialize
946        @mutex.synchronize {
947          unless @initialized
948            @nameserver_port = []
949            @search = nil
950            @ndots = 1
951            case @config_info
952            when nil
953              config_hash = Config.default_config_hash
954            when String
955              config_hash = Config.parse_resolv_conf(@config_info)
956            when Hash
957              config_hash = @config_info.dup
958              if String === config_hash[:nameserver]
959                config_hash[:nameserver] = [config_hash[:nameserver]]
960              end
961              if String === config_hash[:search]
962                config_hash[:search] = [config_hash[:search]]
963              end
964            else
965              raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}")
966            end
967            if config_hash.include? :nameserver
968              @nameserver_port = config_hash[:nameserver].map {|ns| [ns, Port] }
969            end
970            if config_hash.include? :nameserver_port
971              @nameserver_port = config_hash[:nameserver_port].map {|ns, port| [ns, (port || Port)] }
972            end
973            @search = config_hash[:search] if config_hash.include? :search
974            @ndots = config_hash[:ndots] if config_hash.include? :ndots
975
976            if @nameserver_port.empty?
977              @nameserver_port << ['0.0.0.0', Port]
978            end
979            if @search
980              @search = @search.map {|arg| Label.split(arg) }
981            else
982              hostname = Socket.gethostname
983              if /\./ =~ hostname
984                @search = [Label.split($')]
985              else
986                @search = [[]]
987              end
988            end
989
990            if !@nameserver_port.kind_of?(Array) ||
991               @nameserver_port.any? {|ns_port|
992                  !(Array === ns_port) ||
993                  ns_port.length != 2
994                  !(String === ns_port[0]) ||
995                  !(Integer === ns_port[1])
996               }
997              raise ArgumentError.new("invalid nameserver config: #{@nameserver_port.inspect}")
998            end
999
1000            if !@search.kind_of?(Array) ||
1001               !@search.all? {|ls| ls.all? {|l| Label::Str === l } }
1002              raise ArgumentError.new("invalid search config: #{@search.inspect}")
1003            end
1004
1005            if !@ndots.kind_of?(Integer)
1006              raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}")
1007            end
1008
1009            @initialized = true
1010          end
1011        }
1012        self
1013      end
1014
1015      def single?
1016        lazy_initialize
1017        if @nameserver_port.length == 1
1018          return @nameserver_port[0]
1019        else
1020          return nil
1021        end
1022      end
1023
1024      def nameserver_port
1025        @nameserver_port
1026      end
1027
1028      def generate_candidates(name)
1029        candidates = nil
1030        name = Name.create(name)
1031        if name.absolute?
1032          candidates = [name]
1033        else
1034          if @ndots <= name.length - 1
1035            candidates = [Name.new(name.to_a)]
1036          else
1037            candidates = []
1038          end
1039          candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
1040        end
1041        return candidates
1042      end
1043
1044      InitialTimeout = 5
1045
1046      def generate_timeouts
1047        ts = [InitialTimeout]
1048        ts << ts[-1] * 2 / @nameserver_port.length
1049        ts << ts[-1] * 2
1050        ts << ts[-1] * 2
1051        return ts
1052      end
1053
1054      def resolv(name)
1055        candidates = generate_candidates(name)
1056        timeouts = @timeouts || generate_timeouts
1057        begin
1058          candidates.each {|candidate|
1059            begin
1060              timeouts.each {|tout|
1061                @nameserver_port.each {|nameserver, port|
1062                  begin
1063                    yield candidate, tout, nameserver, port
1064                  rescue ResolvTimeout
1065                  end
1066                }
1067              }
1068              raise ResolvError.new("DNS resolv timeout: #{name}")
1069            rescue NXDomain
1070            end
1071          }
1072        rescue ResolvError
1073        end
1074      end
1075
1076      ##
1077      # Indicates no such domain was found.
1078
1079      class NXDomain < ResolvError
1080      end
1081
1082      ##
1083      # Indicates some other unhandled resolver error was encountered.
1084
1085      class OtherResolvError < ResolvError
1086      end
1087    end
1088
1089    module OpCode # :nodoc:
1090      Query = 0
1091      IQuery = 1
1092      Status = 2
1093      Notify = 4
1094      Update = 5
1095    end
1096
1097    module RCode # :nodoc:
1098      NoError = 0
1099      FormErr = 1
1100      ServFail = 2
1101      NXDomain = 3
1102      NotImp = 4
1103      Refused = 5
1104      YXDomain = 6
1105      YXRRSet = 7
1106      NXRRSet = 8
1107      NotAuth = 9
1108      NotZone = 10
1109      BADVERS = 16
1110      BADSIG = 16
1111      BADKEY = 17
1112      BADTIME = 18
1113      BADMODE = 19
1114      BADNAME = 20
1115      BADALG = 21
1116    end
1117
1118    ##
1119    # Indicates that the DNS response was unable to be decoded.
1120
1121    class DecodeError < StandardError
1122    end
1123
1124    ##
1125    # Indicates that the DNS request was unable to be encoded.
1126
1127    class EncodeError < StandardError
1128    end
1129
1130    module Label # :nodoc:
1131      def self.split(arg)
1132        labels = []
1133        arg.scan(/[^\.]+/) {labels << Str.new($&)}
1134        return labels
1135      end
1136
1137      class Str # :nodoc:
1138        def initialize(string)
1139          @string = string
1140          @downcase = string.downcase
1141        end
1142        attr_reader :string, :downcase
1143
1144        def to_s
1145          return @string
1146        end
1147
1148        def inspect
1149          return "#<#{self.class} #{self.to_s}>"
1150        end
1151
1152        def ==(other)
1153          return @downcase == other.downcase
1154        end
1155
1156        def eql?(other)
1157          return self == other
1158        end
1159
1160        def hash
1161          return @downcase.hash
1162        end
1163      end
1164    end
1165
1166    ##
1167    # A representation of a DNS name.
1168
1169    class Name
1170
1171      ##
1172      # Creates a new DNS name from +arg+.  +arg+ can be:
1173      #
1174      # Name:: returns +arg+.
1175      # String:: Creates a new Name.
1176
1177      def self.create(arg)
1178        case arg
1179        when Name
1180          return arg
1181        when String
1182          return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
1183        else
1184          raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
1185        end
1186      end
1187
1188      def initialize(labels, absolute=true) # :nodoc:
1189        @labels = labels
1190        @absolute = absolute
1191      end
1192
1193      def inspect # :nodoc:
1194        "#<#{self.class}: #{self.to_s}#{@absolute ? '.' : ''}>"
1195      end
1196
1197      ##
1198      # True if this name is absolute.
1199
1200      def absolute?
1201        return @absolute
1202      end
1203
1204      def ==(other) # :nodoc:
1205        return false unless Name === other
1206        return @labels.join == other.to_a.join && @absolute == other.absolute?
1207      end
1208
1209      alias eql? == # :nodoc:
1210
1211      ##
1212      # Returns true if +other+ is a subdomain.
1213      #
1214      # Example:
1215      #
1216      #   domain = Resolv::DNS::Name.create("y.z")
1217      #   p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true
1218      #   p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true
1219      #   p Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false
1220      #   p Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false
1221      #   p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false
1222      #   p Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false
1223      #
1224
1225      def subdomain_of?(other)
1226        raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other
1227        return false if @absolute != other.absolute?
1228        other_len = other.length
1229        return false if @labels.length <= other_len
1230        return @labels[-other_len, other_len] == other.to_a
1231      end
1232
1233      def hash # :nodoc:
1234        return @labels.hash ^ @absolute.hash
1235      end
1236
1237      def to_a # :nodoc:
1238        return @labels
1239      end
1240
1241      def length # :nodoc:
1242        return @labels.length
1243      end
1244
1245      def [](i) # :nodoc:
1246        return @labels[i]
1247      end
1248
1249      ##
1250      # returns the domain name as a string.
1251      #
1252      # The domain name doesn't have a trailing dot even if the name object is
1253      # absolute.
1254      #
1255      # Example:
1256      #
1257      #   p Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z"
1258      #   p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z"
1259
1260      def to_s
1261        return @labels.join('.')
1262      end
1263    end
1264
1265    class Message # :nodoc:
1266      @@identifier = -1
1267
1268      def initialize(id = (@@identifier += 1) & 0xffff)
1269        @id = id
1270        @qr = 0
1271        @opcode = 0
1272        @aa = 0
1273        @tc = 0
1274        @rd = 0 # recursion desired
1275        @ra = 0 # recursion available
1276        @rcode = 0
1277        @question = []
1278        @answer = []
1279        @authority = []
1280        @additional = []
1281      end
1282
1283      attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
1284      attr_reader :question, :answer, :authority, :additional
1285
1286      def ==(other)
1287        return @id == other.id &&
1288               @qr == other.qr &&
1289               @opcode == other.opcode &&
1290               @aa == other.aa &&
1291               @tc == other.tc &&
1292               @rd == other.rd &&
1293               @ra == other.ra &&
1294               @rcode == other.rcode &&
1295               @question == other.question &&
1296               @answer == other.answer &&
1297               @authority == other.authority &&
1298               @additional == other.additional
1299      end
1300
1301      def add_question(name, typeclass)
1302        @question << [Name.create(name), typeclass]
1303      end
1304
1305      def each_question
1306        @question.each {|name, typeclass|
1307          yield name, typeclass
1308        }
1309      end
1310
1311      def add_answer(name, ttl, data)
1312        @answer << [Name.create(name), ttl, data]
1313      end
1314
1315      def each_answer
1316        @answer.each {|name, ttl, data|
1317          yield name, ttl, data
1318        }
1319      end
1320
1321      def add_authority(name, ttl, data)
1322        @authority << [Name.create(name), ttl, data]
1323      end
1324
1325      def each_authority
1326        @authority.each {|name, ttl, data|
1327          yield name, ttl, data
1328        }
1329      end
1330
1331      def add_additional(name, ttl, data)
1332        @additional << [Name.create(name), ttl, data]
1333      end
1334
1335      def each_additional
1336        @additional.each {|name, ttl, data|
1337          yield name, ttl, data
1338        }
1339      end
1340
1341      def each_resource
1342        each_answer {|name, ttl, data| yield name, ttl, data}
1343        each_authority {|name, ttl, data| yield name, ttl, data}
1344        each_additional {|name, ttl, data| yield name, ttl, data}
1345      end
1346
1347      def encode
1348        return MessageEncoder.new {|msg|
1349          msg.put_pack('nnnnnn',
1350            @id,
1351            (@qr & 1) << 15 |
1352            (@opcode & 15) << 11 |
1353            (@aa & 1) << 10 |
1354            (@tc & 1) << 9 |
1355            (@rd & 1) << 8 |
1356            (@ra & 1) << 7 |
1357            (@rcode & 15),
1358            @question.length,
1359            @answer.length,
1360            @authority.length,
1361            @additional.length)
1362          @question.each {|q|
1363            name, typeclass = q
1364            msg.put_name(name)
1365            msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue)
1366          }
1367          [@answer, @authority, @additional].each {|rr|
1368            rr.each {|r|
1369              name, ttl, data = r
1370              msg.put_name(name)
1371              msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl)
1372              msg.put_length16 {data.encode_rdata(msg)}
1373            }
1374          }
1375        }.to_s
1376      end
1377
1378      class MessageEncoder # :nodoc:
1379        def initialize
1380          @data = ''
1381          @names = {}
1382          yield self
1383        end
1384
1385        def to_s
1386          return @data
1387        end
1388
1389        def put_bytes(d)
1390          @data << d
1391        end
1392
1393        def put_pack(template, *d)
1394          @data << d.pack(template)
1395        end
1396
1397        def put_length16
1398          length_index = @data.length
1399          @data << "\0\0"
1400          data_start = @data.length
1401          yield
1402          data_end = @data.length
1403          @data[length_index, 2] = [data_end - data_start].pack("n")
1404        end
1405
1406        def put_string(d)
1407          self.put_pack("C", d.length)
1408          @data << d
1409        end
1410
1411        def put_string_list(ds)
1412          ds.each {|d|
1413            self.put_string(d)
1414          }
1415        end
1416
1417        def put_name(d)
1418          put_labels(d.to_a)
1419        end
1420
1421        def put_labels(d)
1422          d.each_index {|i|
1423            domain = d[i..-1]
1424            if idx = @names[domain]
1425              self.put_pack("n", 0xc000 | idx)
1426              return
1427            else
1428              @names[domain] = @data.length
1429              self.put_label(d[i])
1430            end
1431          }
1432          @data << "\0"
1433        end
1434
1435        def put_label(d)
1436          self.put_string(d.to_s)
1437        end
1438      end
1439
1440      def Message.decode(m)
1441        o = Message.new(0)
1442        MessageDecoder.new(m) {|msg|
1443          id, flag, qdcount, ancount, nscount, arcount =
1444            msg.get_unpack('nnnnnn')
1445          o.id = id
1446          o.qr = (flag >> 15) & 1
1447          o.opcode = (flag >> 11) & 15
1448          o.aa = (flag >> 10) & 1
1449          o.tc = (flag >> 9) & 1
1450          o.rd = (flag >> 8) & 1
1451          o.ra = (flag >> 7) & 1
1452          o.rcode = flag & 15
1453          (1..qdcount).each {
1454            name, typeclass = msg.get_question
1455            o.add_question(name, typeclass)
1456          }
1457          (1..ancount).each {
1458            name, ttl, data = msg.get_rr
1459            o.add_answer(name, ttl, data)
1460          }
1461          (1..nscount).each {
1462            name, ttl, data = msg.get_rr
1463            o.add_authority(name, ttl, data)
1464          }
1465          (1..arcount).each {
1466            name, ttl, data = msg.get_rr
1467            o.add_additional(name, ttl, data)
1468          }
1469        }
1470        return o
1471      end
1472
1473      class MessageDecoder # :nodoc:
1474        def initialize(data)
1475          @data = data
1476          @index = 0
1477          @limit = data.length
1478          yield self
1479        end
1480
1481        def inspect
1482          "\#<#{self.class}: #{@data[0, @index].inspect} #{@data[@index..-1].inspect}>"
1483        end
1484
1485        def get_length16
1486          len, = self.get_unpack('n')
1487          save_limit = @limit
1488          @limit = @index + len
1489          d = yield(len)
1490          if @index < @limit
1491            raise DecodeError.new("junk exists")
1492          elsif @limit < @index
1493            raise DecodeError.new("limit exceeded")
1494          end
1495          @limit = save_limit
1496          return d
1497        end
1498
1499        def get_bytes(len = @limit - @index)
1500          raise DecodeError.new("limit exceeded") if @limit < @index + len
1501          d = @data[@index, len]
1502          @index += len
1503          return d
1504        end
1505
1506        def get_unpack(template)
1507          len = 0
1508          template.each_byte {|byte|
1509            byte = "%c" % byte
1510            case byte
1511            when ?c, ?C
1512              len += 1
1513            when ?n
1514              len += 2
1515            when ?N
1516              len += 4
1517            else
1518              raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
1519            end
1520          }
1521          raise DecodeError.new("limit exceeded") if @limit < @index + len
1522          arr = @data.unpack("@#{@index}#{template}")
1523          @index += len
1524          return arr
1525        end
1526
1527        def get_string
1528          raise DecodeError.new("limit exceeded") if @limit <= @index
1529          len = @data[@index].ord
1530          raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len
1531          d = @data[@index + 1, len]
1532          @index += 1 + len
1533          return d
1534        end
1535
1536        def get_string_list
1537          strings = []
1538          while @index < @limit
1539            strings << self.get_string
1540          end
1541          strings
1542        end
1543
1544        def get_name
1545          return Name.new(self.get_labels)
1546        end
1547
1548        def get_labels(limit=nil)
1549          limit = @index if !limit || @index < limit
1550          d = []
1551          while true
1552            raise DecodeError.new("limit exceeded") if @limit <= @index
1553            case @data[@index].ord
1554            when 0
1555              @index += 1
1556              return d
1557            when 192..255
1558              idx = self.get_unpack('n')[0] & 0x3fff
1559              if limit <= idx
1560                raise DecodeError.new("non-backward name pointer")
1561              end
1562              save_index = @index
1563              @index = idx
1564              d += self.get_labels(limit)
1565              @index = save_index
1566              return d
1567            else
1568              d << self.get_label
1569            end
1570          end
1571          return d
1572        end
1573
1574        def get_label
1575          return Label::Str.new(self.get_string)
1576        end
1577
1578        def get_question
1579          name = self.get_name
1580          type, klass = self.get_unpack("nn")
1581          return name, Resource.get_class(type, klass)
1582        end
1583
1584        def get_rr
1585          name = self.get_name
1586          type, klass, ttl = self.get_unpack('nnN')
1587          typeclass = Resource.get_class(type, klass)
1588          res = self.get_length16 { typeclass.decode_rdata self }
1589          res.instance_variable_set :@ttl, ttl
1590          return name, ttl, res
1591        end
1592      end
1593    end
1594
1595    ##
1596    # A DNS query abstract class.
1597
1598    class Query
1599      def encode_rdata(msg) # :nodoc:
1600        raise EncodeError.new("#{self.class} is query.")
1601      end
1602
1603      def self.decode_rdata(msg) # :nodoc:
1604        raise DecodeError.new("#{self.class} is query.")
1605      end
1606    end
1607
1608    ##
1609    # A DNS resource abstract class.
1610
1611    class Resource < Query
1612
1613      ##
1614      # Remaining Time To Live for this Resource.
1615
1616      attr_reader :ttl
1617
1618      ClassHash = {} # :nodoc:
1619
1620      def encode_rdata(msg) # :nodoc:
1621        raise NotImplementedError.new
1622      end
1623
1624      def self.decode_rdata(msg) # :nodoc:
1625        raise NotImplementedError.new
1626      end
1627
1628      def ==(other) # :nodoc:
1629        return false unless self.class == other.class
1630        s_ivars = self.instance_variables
1631        s_ivars.sort!
1632        s_ivars.delete "@ttl"
1633        o_ivars = other.instance_variables
1634        o_ivars.sort!
1635        o_ivars.delete "@ttl"
1636        return s_ivars == o_ivars &&
1637          s_ivars.collect {|name| self.instance_variable_get name} ==
1638            o_ivars.collect {|name| other.instance_variable_get name}
1639      end
1640
1641      def eql?(other) # :nodoc:
1642        return self == other
1643      end
1644
1645      def hash # :nodoc:
1646        h = 0
1647        vars = self.instance_variables
1648        vars.delete "@ttl"
1649        vars.each {|name|
1650          h ^= self.instance_variable_get(name).hash
1651        }
1652        return h
1653      end
1654
1655      def self.get_class(type_value, class_value) # :nodoc:
1656        return ClassHash[[type_value, class_value]] ||
1657               Generic.create(type_value, class_value)
1658      end
1659
1660      ##
1661      # A generic resource abstract class.
1662
1663      class Generic < Resource
1664
1665        ##
1666        # Creates a new generic resource.
1667
1668        def initialize(data)
1669          @data = data
1670        end
1671
1672        ##
1673        # Data for this generic resource.
1674
1675        attr_reader :data
1676
1677        def encode_rdata(msg) # :nodoc:
1678          msg.put_bytes(data)
1679        end
1680
1681        def self.decode_rdata(msg) # :nodoc:
1682          return self.new(msg.get_bytes)
1683        end
1684
1685        def self.create(type_value, class_value) # :nodoc:
1686          c = Class.new(Generic)
1687          c.const_set(:TypeValue, type_value)
1688          c.const_set(:ClassValue, class_value)
1689          Generic.const_set("Type#{type_value}_Class#{class_value}", c)
1690          ClassHash[[type_value, class_value]] = c
1691          return c
1692        end
1693      end
1694
1695      ##
1696      # Domain Name resource abstract class.
1697
1698      class DomainName < Resource
1699
1700        ##
1701        # Creates a new DomainName from +name+.
1702
1703        def initialize(name)
1704          @name = name
1705        end
1706
1707        ##
1708        # The name of this DomainName.
1709
1710        attr_reader :name
1711
1712        def encode_rdata(msg) # :nodoc:
1713          msg.put_name(@name)
1714        end
1715
1716        def self.decode_rdata(msg) # :nodoc:
1717          return self.new(msg.get_name)
1718        end
1719      end
1720
1721      # Standard (class generic) RRs
1722
1723      ClassValue = nil # :nodoc:
1724
1725      ##
1726      # An authoritative name server.
1727
1728      class NS < DomainName
1729        TypeValue = 2 # :nodoc:
1730      end
1731
1732      ##
1733      # The canonical name for an alias.
1734
1735      class CNAME < DomainName
1736        TypeValue = 5 # :nodoc:
1737      end
1738
1739      ##
1740      # Start Of Authority resource.
1741
1742      class SOA < Resource
1743
1744        TypeValue = 6 # :nodoc:
1745
1746        ##
1747        # Creates a new SOA record.  See the attr documentation for the
1748        # details of each argument.
1749
1750        def initialize(mname, rname, serial, refresh, retry_, expire, minimum)
1751          @mname = mname
1752          @rname = rname
1753          @serial = serial
1754          @refresh = refresh
1755          @retry = retry_
1756          @expire = expire
1757          @minimum = minimum
1758        end
1759
1760        ##
1761        # Name of the host where the master zone file for this zone resides.
1762
1763        attr_reader :mname
1764
1765        ##
1766        # The person responsible for this domain name.
1767
1768        attr_reader :rname
1769
1770        ##
1771        # The version number of the zone file.
1772
1773        attr_reader :serial
1774
1775        ##
1776        # How often, in seconds, a secondary name server is to check for
1777        # updates from the primary name server.
1778
1779        attr_reader :refresh
1780
1781        ##
1782        # How often, in seconds, a secondary name server is to retry after a
1783        # failure to check for a refresh.
1784
1785        attr_reader :retry
1786
1787        ##
1788        # Time in seconds that a secondary name server is to use the data
1789        # before refreshing from the primary name server.
1790
1791        attr_reader :expire
1792
1793        ##
1794        # The minimum number of seconds to be used for TTL values in RRs.
1795
1796        attr_reader :minimum
1797
1798        def encode_rdata(msg) # :nodoc:
1799          msg.put_name(@mname)
1800          msg.put_name(@rname)
1801          msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum)
1802        end
1803
1804        def self.decode_rdata(msg) # :nodoc:
1805          mname = msg.get_name
1806          rname = msg.get_name
1807          serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN')
1808          return self.new(
1809            mname, rname, serial, refresh, retry_, expire, minimum)
1810        end
1811      end
1812
1813      ##
1814      # A Pointer to another DNS name.
1815
1816      class PTR < DomainName
1817        TypeValue = 12 # :nodoc:
1818      end
1819
1820      ##
1821      # Host Information resource.
1822
1823      class HINFO < Resource
1824
1825        TypeValue = 13 # :nodoc:
1826
1827        ##
1828        # Creates a new HINFO running +os+ on +cpu+.
1829
1830        def initialize(cpu, os)
1831          @cpu = cpu
1832          @os = os
1833        end
1834
1835        ##
1836        # CPU architecture for this resource.
1837
1838        attr_reader :cpu
1839
1840        ##
1841        # Operating system for this resource.
1842
1843        attr_reader :os
1844
1845        def encode_rdata(msg) # :nodoc:
1846          msg.put_string(@cpu)
1847          msg.put_string(@os)
1848        end
1849
1850        def self.decode_rdata(msg) # :nodoc:
1851          cpu = msg.get_string
1852          os = msg.get_string
1853          return self.new(cpu, os)
1854        end
1855      end
1856
1857      ##
1858      # Mailing list or mailbox information.
1859
1860      class MINFO < Resource
1861
1862        TypeValue = 14 # :nodoc:
1863
1864        def initialize(rmailbx, emailbx)
1865          @rmailbx = rmailbx
1866          @emailbx = emailbx
1867        end
1868
1869        ##
1870        # Domain name responsible for this mail list or mailbox.
1871
1872        attr_reader :rmailbx
1873
1874        ##
1875        # Mailbox to use for error messages related to the mail list or mailbox.
1876
1877        attr_reader :emailbx
1878
1879        def encode_rdata(msg) # :nodoc:
1880          msg.put_name(@rmailbx)
1881          msg.put_name(@emailbx)
1882        end
1883
1884        def self.decode_rdata(msg) # :nodoc:
1885          rmailbx = msg.get_string
1886          emailbx = msg.get_string
1887          return self.new(rmailbx, emailbx)
1888        end
1889      end
1890
1891      ##
1892      # Mail Exchanger resource.
1893
1894      class MX < Resource
1895
1896        TypeValue= 15 # :nodoc:
1897
1898        ##
1899        # Creates a new MX record with +preference+, accepting mail at
1900        # +exchange+.
1901
1902        def initialize(preference, exchange)
1903          @preference = preference
1904          @exchange = exchange
1905        end
1906
1907        ##
1908        # The preference for this MX.
1909
1910        attr_reader :preference
1911
1912        ##
1913        # The host of this MX.
1914
1915        attr_reader :exchange
1916
1917        def encode_rdata(msg) # :nodoc:
1918          msg.put_pack('n', @preference)
1919          msg.put_name(@exchange)
1920        end
1921
1922        def self.decode_rdata(msg) # :nodoc:
1923          preference, = msg.get_unpack('n')
1924          exchange = msg.get_name
1925          return self.new(preference, exchange)
1926        end
1927      end
1928
1929      ##
1930      # Unstructured text resource.
1931
1932      class TXT < Resource
1933
1934        TypeValue = 16 # :nodoc:
1935
1936        def initialize(first_string, *rest_strings)
1937          @strings = [first_string, *rest_strings]
1938        end
1939
1940        ##
1941        # Returns an Array of Strings for this TXT record.
1942
1943        attr_reader :strings
1944
1945        ##
1946        # Returns the concatenated string from +strings+.
1947
1948        def data
1949          @strings.join("")
1950        end
1951
1952        def encode_rdata(msg) # :nodoc:
1953          msg.put_string_list(@strings)
1954        end
1955
1956        def self.decode_rdata(msg) # :nodoc:
1957          strings = msg.get_string_list
1958          return self.new(*strings)
1959        end
1960      end
1961
1962      ##
1963      # A Query type requesting any RR.
1964
1965      class ANY < Query
1966        TypeValue = 255 # :nodoc:
1967      end
1968
1969      ClassInsensitiveTypes = [ # :nodoc:
1970        NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, ANY
1971      ]
1972
1973      ##
1974      # module IN contains ARPA Internet specific RRs.
1975
1976      module IN
1977
1978        ClassValue = 1 # :nodoc:
1979
1980        ClassInsensitiveTypes.each {|s|
1981          c = Class.new(s)
1982          c.const_set(:TypeValue, s::TypeValue)
1983          c.const_set(:ClassValue, ClassValue)
1984          ClassHash[[s::TypeValue, ClassValue]] = c
1985          self.const_set(s.name.sub(/.*::/, ''), c)
1986        }
1987
1988        ##
1989        # IPv4 Address resource
1990
1991        class A < Resource
1992          TypeValue = 1
1993          ClassValue = IN::ClassValue
1994          ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
1995
1996          ##
1997          # Creates a new A for +address+.
1998
1999          def initialize(address)
2000            @address = IPv4.create(address)
2001          end
2002
2003          ##
2004          # The Resolv::IPv4 address for this A.
2005
2006          attr_reader :address
2007
2008          def encode_rdata(msg) # :nodoc:
2009            msg.put_bytes(@address.address)
2010          end
2011
2012          def self.decode_rdata(msg) # :nodoc:
2013            return self.new(IPv4.new(msg.get_bytes(4)))
2014          end
2015        end
2016
2017        ##
2018        # Well Known Service resource.
2019
2020        class WKS < Resource
2021          TypeValue = 11
2022          ClassValue = IN::ClassValue
2023          ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
2024
2025          def initialize(address, protocol, bitmap)
2026            @address = IPv4.create(address)
2027            @protocol = protocol
2028            @bitmap = bitmap
2029          end
2030
2031          ##
2032          # The host these services run on.
2033
2034          attr_reader :address
2035
2036          ##
2037          # IP protocol number for these services.
2038
2039          attr_reader :protocol
2040
2041          ##
2042          # A bit map of enabled services on this host.
2043          #
2044          # If protocol is 6 (TCP) then the 26th bit corresponds to the SMTP
2045          # service (port 25).  If this bit is set, then an SMTP server should
2046          # be listening on TCP port 25; if zero, SMTP service is not
2047          # supported.
2048
2049          attr_reader :bitmap
2050
2051          def encode_rdata(msg) # :nodoc:
2052            msg.put_bytes(@address.address)
2053            msg.put_pack("n", @protocol)
2054            msg.put_bytes(@bitmap)
2055          end
2056
2057          def self.decode_rdata(msg) # :nodoc:
2058            address = IPv4.new(msg.get_bytes(4))
2059            protocol, = msg.get_unpack("n")
2060            bitmap = msg.get_bytes
2061            return self.new(address, protocol, bitmap)
2062          end
2063        end
2064
2065        ##
2066        # An IPv6 address record.
2067
2068        class AAAA < Resource
2069          TypeValue = 28
2070          ClassValue = IN::ClassValue
2071          ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
2072
2073          ##
2074          # Creates a new AAAA for +address+.
2075
2076          def initialize(address)
2077            @address = IPv6.create(address)
2078          end
2079
2080          ##
2081          # The Resolv::IPv6 address for this AAAA.
2082
2083          attr_reader :address
2084
2085          def encode_rdata(msg) # :nodoc:
2086            msg.put_bytes(@address.address)
2087          end
2088
2089          def self.decode_rdata(msg) # :nodoc:
2090            return self.new(IPv6.new(msg.get_bytes(16)))
2091          end
2092        end
2093
2094        ##
2095        # SRV resource record defined in RFC 2782
2096        #
2097        # These records identify the hostname and port that a service is
2098        # available at.
2099
2100        class SRV < Resource
2101          TypeValue = 33
2102          ClassValue = IN::ClassValue
2103          ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
2104
2105          # Create a SRV resource record.
2106          #
2107          # See the documentation for #priority, #weight, #port and #target
2108          # for +priority+, +weight+, +port and +target+ respectively.
2109
2110          def initialize(priority, weight, port, target)
2111            @priority = priority.to_int
2112            @weight = weight.to_int
2113            @port = port.to_int
2114            @target = Name.create(target)
2115          end
2116
2117          # The priority of this target host.
2118          #
2119          # A client MUST attempt to contact the target host with the
2120          # lowest-numbered priority it can reach; target hosts with the same
2121          # priority SHOULD be tried in an order defined by the weight field.
2122          # The range is 0-65535.  Note that it is not widely implemented and
2123          # should be set to zero.
2124
2125          attr_reader :priority
2126
2127          # A server selection mechanism.
2128          #
2129          # The weight field specifies a relative weight for entries with the
2130          # same priority. Larger weights SHOULD be given a proportionately
2131          # higher probability of being selected. The range of this number is
2132          # 0-65535.  Domain administrators SHOULD use Weight 0 when there
2133          # isn't any server selection to do, to make the RR easier to read
2134          # for humans (less noisy). Note that it is not widely implemented
2135          # and should be set to zero.
2136
2137          attr_reader :weight
2138
2139          # The port on this target host of this service.
2140          #
2141          # The range is 0-65535.
2142
2143          attr_reader :port
2144
2145          # The domain name of the target host.
2146          #
2147          # A target of "." means that the service is decidedly not available
2148          # at this domain.
2149
2150          attr_reader :target
2151
2152          def encode_rdata(msg) # :nodoc:
2153            msg.put_pack("n", @priority)
2154            msg.put_pack("n", @weight)
2155            msg.put_pack("n", @port)
2156            msg.put_name(@target)
2157          end
2158
2159          def self.decode_rdata(msg) # :nodoc:
2160            priority, = msg.get_unpack("n")
2161            weight,   = msg.get_unpack("n")
2162            port,     = msg.get_unpack("n")
2163            target    = msg.get_name
2164            return self.new(priority, weight, port, target)
2165          end
2166        end
2167      end
2168    end
2169  end
2170
2171  ##
2172  # A Resolv::DNS IPv4 address.
2173
2174  class IPv4
2175
2176    ##
2177    # Regular expression IPv4 addresses must match.
2178
2179    Regex256 = /0
2180               |1(?:[0-9][0-9]?)?
2181               |2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
2182               |[3-9][0-9]?/x
2183    Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/
2184
2185    def self.create(arg)
2186      case arg
2187      when IPv4
2188        return arg
2189      when Regex
2190        if (0..255) === (a = $1.to_i) &&
2191           (0..255) === (b = $2.to_i) &&
2192           (0..255) === (c = $3.to_i) &&
2193           (0..255) === (d = $4.to_i)
2194          return self.new([a, b, c, d].pack("CCCC"))
2195        else
2196          raise ArgumentError.new("IPv4 address with invalid value: " + arg)
2197        end
2198      else
2199        raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}")
2200      end
2201    end
2202
2203    def initialize(address) # :nodoc:
2204      unless address.kind_of?(String)
2205        raise ArgumentError, 'IPv4 address must be a string'
2206      end
2207      unless address.length == 4
2208        raise ArgumentError, "IPv4 address expects 4 bytes but #{address.length} bytes"
2209      end
2210      @address = address
2211    end
2212
2213    ##
2214    # A String representation of this IPv4 address.
2215
2216    ##
2217    # The raw IPv4 address as a String.
2218
2219    attr_reader :address
2220
2221    def to_s # :nodoc:
2222      return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
2223    end
2224
2225    def inspect # :nodoc:
2226      return "#<#{self.class} #{self.to_s}>"
2227    end
2228
2229    ##
2230    # Turns this IPv4 address into a Resolv::DNS::Name.
2231
2232    def to_name
2233      return DNS::Name.create(
2234        '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
2235    end
2236
2237    def ==(other) # :nodoc:
2238      return @address == other.address
2239    end
2240
2241    def eql?(other) # :nodoc:
2242      return self == other
2243    end
2244
2245    def hash # :nodoc:
2246      return @address.hash
2247    end
2248  end
2249
2250  ##
2251  # A Resolv::DNS IPv6 address.
2252
2253  class IPv6
2254
2255    ##
2256    # IPv6 address format a:b:c:d:e:f:g:h
2257    Regex_8Hex = /\A
2258      (?:[0-9A-Fa-f]{1,4}:){7}
2259         [0-9A-Fa-f]{1,4}
2260      \z/x
2261
2262    ##
2263    # Compressed IPv6 address format a::b
2264
2265    Regex_CompressedHex = /\A
2266      ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
2267      ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
2268      \z/x
2269
2270    ##
2271    # IPv4 mapped IPv6 address format a:b:c:d:e:f:w.x.y.z
2272
2273    Regex_6Hex4Dec = /\A
2274      ((?:[0-9A-Fa-f]{1,4}:){6,6})
2275      (\d+)\.(\d+)\.(\d+)\.(\d+)
2276      \z/x
2277
2278    ##
2279    # Compressed IPv4 mapped IPv6 address format a::b:w.x.y.z
2280
2281    Regex_CompressedHex4Dec = /\A
2282      ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
2283      ((?:[0-9A-Fa-f]{1,4}:)*)
2284      (\d+)\.(\d+)\.(\d+)\.(\d+)
2285      \z/x
2286
2287    ##
2288    # A composite IPv6 address Regexp.
2289
2290    Regex = /
2291      (?:#{Regex_8Hex}) |
2292      (?:#{Regex_CompressedHex}) |
2293      (?:#{Regex_6Hex4Dec}) |
2294      (?:#{Regex_CompressedHex4Dec})/x
2295
2296    ##
2297    # Creates a new IPv6 address from +arg+ which may be:
2298    #
2299    # IPv6:: returns +arg+.
2300    # String:: +arg+ must match one of the IPv6::Regex* constants
2301
2302    def self.create(arg)
2303      case arg
2304      when IPv6
2305        return arg
2306      when String
2307        address = ''
2308        if Regex_8Hex =~ arg
2309          arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
2310        elsif Regex_CompressedHex =~ arg
2311          prefix = $1
2312          suffix = $2
2313          a1 = ''
2314          a2 = ''
2315          prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
2316          suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
2317          omitlen = 16 - a1.length - a2.length
2318          address << a1 << "\0" * omitlen << a2
2319        elsif Regex_6Hex4Dec =~ arg
2320          prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
2321          if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
2322            prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
2323            address << [a, b, c, d].pack('CCCC')
2324          else
2325            raise ArgumentError.new("not numeric IPv6 address: " + arg)
2326          end
2327        elsif Regex_CompressedHex4Dec =~ arg
2328          prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i
2329          if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
2330            a1 = ''
2331            a2 = ''
2332            prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
2333            suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
2334            omitlen = 12 - a1.length - a2.length
2335            address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
2336          else
2337            raise ArgumentError.new("not numeric IPv6 address: " + arg)
2338          end
2339        else
2340          raise ArgumentError.new("not numeric IPv6 address: " + arg)
2341        end
2342        return IPv6.new(address)
2343      else
2344        raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}")
2345      end
2346    end
2347
2348    def initialize(address) # :nodoc:
2349      unless address.kind_of?(String) && address.length == 16
2350        raise ArgumentError.new('IPv6 address must be 16 bytes')
2351      end
2352      @address = address
2353    end
2354
2355    ##
2356    # The raw IPv6 address as a String.
2357
2358    attr_reader :address
2359
2360    def to_s # :nodoc:
2361      address = sprintf("%X:%X:%X:%X:%X:%X:%X:%X", *@address.unpack("nnnnnnnn"))
2362      unless address.sub!(/(^|:)0(:0)+(:|$)/, '::')
2363        address.sub!(/(^|:)0(:|$)/, '::')
2364      end
2365      return address
2366    end
2367
2368    def inspect # :nodoc:
2369      return "#<#{self.class} #{self.to_s}>"
2370    end
2371
2372    ##
2373    # Turns this IPv6 address into a Resolv::DNS::Name.
2374    #--
2375    # ip6.arpa should be searched too. [RFC3152]
2376
2377    def to_name
2378      return DNS::Name.new(
2379        @address.unpack("H32")[0].split(//).reverse + ['ip6', 'arpa'])
2380    end
2381
2382    def ==(other) # :nodoc:
2383      return @address == other.address
2384    end
2385
2386    def eql?(other) # :nodoc:
2387      return self == other
2388    end
2389
2390    def hash # :nodoc:
2391      return @address.hash
2392    end
2393  end
2394
2395  ##
2396  # Default resolver to use for Resolv class methods.
2397
2398  DefaultResolver = self.new
2399
2400  ##
2401  # Address Regexp to use for matching IP addresses.
2402
2403  AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/
2404
2405end
2406
2407