PageRenderTime 28ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/net/dns/resolver.rb

https://bitbucket.org/technopunk2099/metasploit-framework
Ruby | 1285 lines | 628 code | 109 blank | 548 comment | 61 complexity | 9ba6031f16b5ce5710b558a9b1c317f0 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, LGPL-2.1, GPL-2.0, MIT
  1. # -*- coding: binary -*-
  2. #
  3. # $Id: Resolver.rb,v 1.11 2006/07/30 16:55:35 bluemonk Exp $
  4. #
  5. require 'socket'
  6. require 'timeout'
  7. require 'ipaddr'
  8. require 'logger'
  9. require 'net/dns/packet'
  10. require 'net/dns/resolver/timeouts'
  11. alias old_send send
  12. module Net # :nodoc:
  13. module DNS
  14. include Logger::Severity
  15. # =Name
  16. #
  17. # Net::DNS::Resolver - DNS resolver class
  18. #
  19. # =Synopsis
  20. #
  21. # require 'net/dns/resolver'
  22. #
  23. # =Description
  24. #
  25. # The Net::DNS::Resolver class implements a complete DNS resolver written
  26. # in pure Ruby, without a single C line of code. It has all of the
  27. # tipical properties of an evoluted resolver, and a bit of OO which
  28. # comes from having used Ruby.
  29. #
  30. # This project started as a porting of the Net::DNS Perl module,
  31. # written by Martin Fuhr, but turned out (in the last months) to be
  32. # an almost complete rewriting. Well, maybe some of the features of
  33. # the Perl version are still missing, but guys, at least this is
  34. # readable code!
  35. #
  36. # FIXME
  37. #
  38. # =Environment
  39. #
  40. # The Following Environment variables can also be used to configure
  41. # the resolver:
  42. #
  43. # * +RES_NAMESERVERS+: A space-separated list of nameservers to query.
  44. #
  45. # # Bourne Shell
  46. # $ RES_NAMESERVERS="192.168.1.1 192.168.2.2 192.168.3.3"
  47. # $ export RES_NAMESERVERS
  48. #
  49. # # C Shell
  50. # % setenv RES_NAMESERVERS "192.168.1.1 192.168.2.2 192.168.3.3"
  51. #
  52. # * +RES_SEARCHLIST+: A space-separated list of domains to put in the
  53. # search list.
  54. #
  55. # # Bourne Shell
  56. # $ RES_SEARCHLIST="example.com sub1.example.com sub2.example.com"
  57. # $ export RES_SEARCHLIST
  58. #
  59. # # C Shell
  60. # % setenv RES_SEARCHLIST "example.com sub1.example.com sub2.example.com"
  61. #
  62. # * +LOCALDOMAIN+: The default domain.
  63. #
  64. # # Bourne Shell
  65. # $ LOCALDOMAIN=example.com
  66. # $ export LOCALDOMAIN
  67. #
  68. # # C Shell
  69. # % setenv LOCALDOMAIN example.com
  70. #
  71. # * +RES_OPTIONS+: A space-separated list of resolver options to set.
  72. # Options that take values are specified as option:value.
  73. #
  74. # # Bourne Shell
  75. # $ RES_OPTIONS="retrans:3 retry:2 debug"
  76. # $ export RES_OPTIONS
  77. #
  78. # # C Shell
  79. # % setenv RES_OPTIONS "retrans:3 retry:2 debug"
  80. #
  81. class Resolver
  82. # An hash with the defaults values of almost all the
  83. # configuration parameters of a resolver object. See
  84. # the description for each parameter to have an
  85. # explanation of its usage.
  86. Defaults = {
  87. :config_file => "/etc/resolv.conf",
  88. :log_file => $stdout,
  89. :port => 53,
  90. :searchlist => [],
  91. :nameservers => [IPAddr.new("127.0.0.1")],
  92. :domain => "",
  93. :source_port => 0,
  94. :source_address => IPAddr.new("0.0.0.0"),
  95. :retry_interval => 5,
  96. :retry_number => 4,
  97. :recursive => true,
  98. :defname => true,
  99. :dns_search => true,
  100. :use_tcp => false,
  101. :ignore_truncated => false,
  102. :packet_size => 512,
  103. :tcp_timeout => TcpTimeout.new(120),
  104. :udp_timeout => UdpTimeout.new(0)}
  105. # Create a new resolver object.
  106. #
  107. # Argument +config+ can either be empty or be an hash with
  108. # some configuration parameters. To know what each parameter
  109. # do, look at the description of each.
  110. # Some example:
  111. #
  112. # # Use the sistem defaults
  113. # res = Net::DNS::Resolver.new
  114. #
  115. # # Specify a configuration file
  116. # res = Net::DNS::Resolver.new(:config_file => '/my/dns.conf')
  117. #
  118. # # Set some option
  119. # res = Net::DNS::Resolver.new(:nameservers => "172.16.1.1",
  120. # :recursive => false,
  121. # :retry => 10)
  122. #
  123. # ===Config file
  124. #
  125. # Net::DNS::Resolver uses a config file to read the usual
  126. # values a resolver needs, such as nameserver list and
  127. # domain names. On UNIX systems the defaults are read from the
  128. # following files, in the order indicated:
  129. #
  130. # * /etc/resolv.conf
  131. # * $HOME/.resolv.conf
  132. # * ./.resolv.conf
  133. #
  134. # The following keywords are recognized in resolver configuration files:
  135. #
  136. # * domain: the default domain.
  137. # * search: a space-separated list of domains to put in the search list.
  138. # * nameserver: a space-separated list of nameservers to query.
  139. #
  140. # Files except for /etc/resolv.conf must be owned by the effective userid
  141. # running the program or they won't be read. In addition, several environment
  142. # variables can also contain configuration information; see Environment
  143. # in the main description for Resolver class.
  144. #
  145. # On Windows Systems, an attempt is made to determine the system defaults
  146. # using the registry. This is still a work in progress; systems with many
  147. # dynamically configured network interfaces may confuse Net::DNS.
  148. #
  149. # You can include a configuration file of your own when creating a resolver
  150. # object:
  151. #
  152. # # Use my own configuration file
  153. # my $res = Net::DNS::Resolver->new(config_file => '/my/dns.conf');
  154. #
  155. # This is supported on both UNIX and Windows. Values pulled from a custom
  156. # configuration file override the the system's defaults, but can still be
  157. # overridden by the other arguments to Resolver::new.
  158. #
  159. # Explicit arguments to Resolver::new override both the system's defaults
  160. # and the values of the custom configuration file, if any.
  161. #
  162. # ===Parameters
  163. #
  164. # The following arguments to Resolver::new are supported:
  165. #
  166. # - nameservers: an array reference of nameservers to query.
  167. # - searchlist: an array reference of domains.
  168. # - recurse
  169. # - debug
  170. # - domain
  171. # - port
  172. # - srcaddr
  173. # - srcport
  174. # - tcp_timeout
  175. # - udp_timeout
  176. # - retrans
  177. # - retry
  178. # - usevc
  179. # - stayopen
  180. # - igntc
  181. # - defnames
  182. # - dnsrch
  183. # - persistent_tcp
  184. # - persistent_udp
  185. # - dnssec
  186. #
  187. # For more information on any of these options, please consult the
  188. # method of the same name.
  189. #
  190. # ===Disclaimer
  191. #
  192. # Part of the above documentation is taken from the one in the
  193. # Net::DNS::Resolver Perl module.
  194. #
  195. def initialize(config = {})
  196. raise ResolverArgumentError, "Argument has to be Hash" unless config.kind_of? Hash
  197. # config.key_downcase!
  198. @config = Defaults.merge config
  199. @raw = false
  200. # New logger facility
  201. @logger = Logger.new(@config[:log_file])
  202. @logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
  203. #------------------------------------------------------------
  204. # Resolver configuration will be set in order from:
  205. # 1) initialize arguments
  206. # 2) ENV variables
  207. # 3) config file
  208. # 4) defaults (and /etc/resolv.conf for config)
  209. #------------------------------------------------------------
  210. #------------------------------------------------------------
  211. # Parsing config file
  212. #------------------------------------------------------------
  213. parse_config_file
  214. #------------------------------------------------------------
  215. # Parsing ENV variables
  216. #------------------------------------------------------------
  217. parse_environment_variables
  218. #------------------------------------------------------------
  219. # Parsing arguments
  220. #------------------------------------------------------------
  221. config.each do |key,val|
  222. next if key == :log_file or key == :config_file
  223. begin
  224. eval "self.#{key.to_s} = val"
  225. rescue NoMethodError
  226. raise ResolverArgumentError, "Option #{key} not valid"
  227. end
  228. end
  229. end
  230. # Get the resolver searchlist, returned as an array of entries
  231. #
  232. # res.searchlist
  233. # #=> ["example.com","a.example.com","b.example.com"]
  234. #
  235. def searchlist
  236. @config[:searchlist].inspect
  237. end
  238. # Set the resolver searchlist.
  239. # +arg+ can be a single string or an array of strings
  240. #
  241. # res.searchstring = "example.com"
  242. # res.searchstring = ["example.com","a.example.com","b.example.com"]
  243. #
  244. # Note that you can also append a new name to the searchlist
  245. #
  246. # res.searchlist << "c.example.com"
  247. # res.searchlist
  248. # #=> ["example.com","a.example.com","b.example.com","c.example.com"]
  249. #
  250. # The default is an empty array
  251. #
  252. def searchlist=(arg)
  253. case arg
  254. when String
  255. @config[:searchlist] = [arg] if valid? arg
  256. @logger.info "Searchlist changed to value #{@config[:searchlist].inspect}"
  257. when Array
  258. @config[:searchlist] = arg if arg.all? {|x| valid? x}
  259. @logger.info "Searchlist changed to value #{@config[:searchlist].inspect}"
  260. else
  261. raise ResolverArgumentError, "Wrong argument format, neither String nor Array"
  262. end
  263. end
  264. # Get the list of resolver nameservers, in a dotted decimal format
  265. #
  266. # res.nameservers
  267. # #=> ["192.168.0.1","192.168.0.2"]
  268. #
  269. def nameservers
  270. arr = []
  271. @config[:nameservers].each do |x|
  272. arr << x.to_s
  273. end
  274. arr
  275. end
  276. alias_method :nameserver, :nameservers
  277. # Set the list of resolver nameservers
  278. # +arg+ can be a single ip address or an array of addresses
  279. #
  280. # res.nameservers = "192.168.0.1"
  281. # res.nameservers = ["192.168.0.1","192.168.0.2"]
  282. #
  283. # If you want you can specify the addresses as IPAddr instances
  284. #
  285. # ip = IPAddr.new("192.168.0.3")
  286. # res.nameservers << ip
  287. # #=> ["192.168.0.1","192.168.0.2","192.168.0.3"]
  288. #
  289. # The default is 127.0.0.1 (localhost)
  290. #
  291. def nameservers=(arg)
  292. case arg
  293. when String
  294. begin
  295. @config[:nameservers] = [IPAddr.new(arg)]
  296. @logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"
  297. rescue ArgumentError # arg is in the name form, not IP
  298. nameservers_from_name(arg)
  299. end
  300. when IPAddr
  301. @config[:nameservers] = [arg]
  302. @logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"
  303. when Array
  304. @config[:nameservers] = []
  305. arg.each do |x|
  306. @config[:nameservers] << case x
  307. when String
  308. begin
  309. IPAddr.new(x)
  310. rescue ArgumentError
  311. nameservers_from_name(arg)
  312. return
  313. end
  314. when IPAddr
  315. x
  316. else
  317. raise ResolverArgumentError, "Wrong argument format"
  318. end
  319. end
  320. @logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"
  321. else
  322. raise ResolverArgumentError, "Wrong argument format, neither String, Array nor IPAddr"
  323. end
  324. end
  325. alias_method("nameserver=","nameservers=")
  326. # Return a string with the default domain
  327. #
  328. def domain
  329. @config[:domain].inspect
  330. end
  331. # Set the domain for the query
  332. #
  333. def domain=(name)
  334. @config[:domain] = name if valid? name
  335. end
  336. # Return the defined size of the packet
  337. #
  338. def packet_size
  339. @config[:packet_size]
  340. end
  341. # Get the port number to which the resolver sends queries.
  342. #
  343. # puts "Sending queries to port #{res.port}"
  344. #
  345. def port
  346. @config[:port]
  347. end
  348. # Set the port number to which the resolver sends queries. This can be useful
  349. # for testing a nameserver running on a non-standard port.
  350. #
  351. # res.port = 10053
  352. #
  353. # The default is port 53.
  354. #
  355. def port=(num)
  356. if (0..65535).include? num
  357. @config[:port] = num
  358. @logger.info "Port number changed to #{num}"
  359. else
  360. raise ResolverArgumentError, "Wrong port number #{num}"
  361. end
  362. end
  363. # Get the value of the source port number
  364. #
  365. # puts "Sending queries using port #{res.source_port}"
  366. #
  367. def source_port
  368. @config[:source_port]
  369. end
  370. alias srcport source_port
  371. # Set the local source port from which the resolver sends its queries.
  372. #
  373. # res.source_port = 40000
  374. #
  375. # Note that if you want to set a port you need root priviledges, as
  376. # raw sockets will be used to generate packets. The class will then
  377. # generate the exception ResolverPermissionError if you're not root.
  378. #
  379. # The default is 0, which means that the port will be chosen by the
  380. # underlaying layers.
  381. #
  382. def source_port=(num)
  383. unless root?
  384. raise ResolverPermissionError, "Are you root?"
  385. end
  386. if (0..65535).include?(num)
  387. @config[:source_port] = num
  388. else
  389. raise ResolverArgumentError, "Wrong port number #{num}"
  390. end
  391. end
  392. alias srcport= source_port=
  393. # Get the local address from which the resolver sends queries
  394. #
  395. # puts "Sending queries using source address #{res.source_address}"
  396. #
  397. def source_address
  398. @config[:source_address].to_s
  399. end
  400. alias srcaddr source_address
  401. # Set the local source address from which the resolver sends its
  402. # queries.
  403. #
  404. # res.source_address = "172.16.100.1"
  405. # res.source_address = IPAddr.new("172.16.100.1")
  406. #
  407. # You can specify +arg+ as either a string containing the ip address
  408. # or an instance of IPAddr class.
  409. #
  410. # Normally this can be used to force queries out a specific interface
  411. # on a multi-homed host. In this case, you should of course need to
  412. # know the addresses of the interfaces.
  413. #
  414. # Another way to use this option is for some kind of spoofing attacks
  415. # towards weak nameservers, to probe the security of your network.
  416. # This includes specifing ranged attacks such as DoS and others. For
  417. # a paper on DNS security, checks http://www.marcoceresa.com/security/
  418. #
  419. # Note that if you want to set a non-binded source address you need
  420. # root priviledges, as raw sockets will be used to generate packets.
  421. # The class will then generate an exception if you're not root.
  422. #
  423. # The default is 0.0.0.0, meaning any local address (chosen on routing
  424. # needs).
  425. #
  426. def source_address=(addr)
  427. unless addr.respond_to? :to_s
  428. raise ResolverArgumentError, "Wrong address argument #{addr}"
  429. end
  430. begin
  431. port = rand(64000)+1024
  432. @logger.warn "Try to determine state of source address #{addr} with port #{port}"
  433. a = TCPServer.new(addr.to_s,port)
  434. rescue SystemCallError => e
  435. case e.errno
  436. when 98 # Port already in use!
  437. @logger.warn "Port already in use"
  438. retry
  439. when 99 # Address is not valid: raw socket
  440. @raw = true
  441. @logger.warn "Using raw sockets"
  442. else
  443. raise SystemCallError, e
  444. end
  445. ensure
  446. a.close
  447. end
  448. case addr
  449. when String
  450. @config[:source_address] = IPAddr.new(string)
  451. @logger.info "Using new source address: #{@config[:source_address]}"
  452. when IPAddr
  453. @config[:source_address] = addr
  454. @logger.info "Using new source address: #{@config[:source_address]}"
  455. else
  456. raise ArgumentError, "Unknown dest_address format"
  457. end
  458. end
  459. alias srcaddr= source_address=
  460. # Return the retrasmission interval (in seconds) the resolvers has
  461. # been set on
  462. #
  463. def retry_interval
  464. @config[:retry_interval]
  465. end
  466. alias retrans retry_interval
  467. # Set the retrasmission interval in seconds. Default 5 seconds
  468. #
  469. def retry_interval=(num)
  470. if num > 0
  471. @config[:retry_interval] = num
  472. @logger.info "Retransmission interval changed to #{num} seconds"
  473. else
  474. raise ResolverArgumentError, "Interval must be positive"
  475. end
  476. end
  477. alias retrans= retry_interval=
  478. # The number of times the resolver will try a query
  479. #
  480. # puts "Will try a max of #{res.retry_number} queries"
  481. #
  482. def retry_number
  483. @config[:retry_number]
  484. end
  485. # Set the number of times the resolver will try a query.
  486. # Default 4 times
  487. #
  488. def retry_number=(num)
  489. if num.kind_of? Integer and num > 0
  490. @config[:retry_number] = num
  491. @logger.info "Retrasmissions number changed to #{num}"
  492. else
  493. raise ResolverArgumentError, "Retry value must be a positive integer"
  494. end
  495. end
  496. alias_method('retry=', 'retry_number=')
  497. # This method will return true if the resolver is configured to
  498. # perform recursive queries.
  499. #
  500. # print "The resolver will perform a "
  501. # print res.recursive? ? "" : "not "
  502. # puts "recursive query"
  503. #
  504. def recursive?
  505. @config[:recursive]
  506. end
  507. alias_method :recurse, :recursive?
  508. alias_method :recursive, :recursive?
  509. # Sets whether or not the resolver should perform recursive
  510. # queries. Default is true.
  511. #
  512. # res.recursive = false # perform non-recursive query
  513. #
  514. def recursive=(bool)
  515. case bool
  516. when TrueClass,FalseClass
  517. @config[:recursive] = bool
  518. @logger.info("Recursive state changed to #{bool}")
  519. else
  520. raise ResolverArgumentError, "Argument must be boolean"
  521. end
  522. end
  523. alias_method :recurse=, :recursive=
  524. # Return a string rapresenting the resolver state, suitable
  525. # for printing on the screen.
  526. #
  527. # puts "Resolver state:"
  528. # puts res.state
  529. #
  530. def state
  531. str = ";; RESOLVER state:\n;; "
  532. i = 1
  533. @config.each do |key,val|
  534. if key == :log_file or key == :config_file
  535. str << "#{key}: #{val} \t"
  536. else
  537. str << "#{key}: #{eval(key.to_s)} \t"
  538. end
  539. str << "\n;; " if i % 2 == 0
  540. i += 1
  541. end
  542. str
  543. end
  544. alias print state
  545. alias inspect state
  546. # Checks whether the +defname+ flag has been activate.
  547. def defname?
  548. @config[:defname]
  549. end
  550. alias defname defname?
  551. # Set the flag +defname+ in a boolean state. if +defname+ is true,
  552. # calls to Resolver#query will append the default domain to names
  553. # that contain no dots.
  554. # Example:
  555. #
  556. # # Domain example.com
  557. # res.defname = true
  558. # res.query("machine1")
  559. # #=> This will perform a query for machine1.example.com
  560. #
  561. # Default is true.
  562. #
  563. def defname=(bool)
  564. case bool
  565. when TrueClass,FalseClass
  566. @config[:defname] = bool
  567. @logger.info("Defname state changed to #{bool}")
  568. else
  569. raise ResolverArgumentError, "Argument must be boolean"
  570. end
  571. end
  572. # Get the state of the dns_search flag
  573. def dns_search
  574. @config[:dns_search]
  575. end
  576. alias_method :dnsrch, :dns_search
  577. # Set the flag +dns_search+ in a boolean state. If +dns_search+
  578. # is true, when using the Resolver#search method will be applied
  579. # the search list. Default is true.
  580. #
  581. def dns_search=(bool)
  582. case bool
  583. when TrueClass,FalseClass
  584. @config[:dns_search] = bool
  585. @logger.info("DNS search state changed to #{bool}")
  586. else
  587. raise ResolverArgumentError, "Argument must be boolean"
  588. end
  589. end
  590. alias_method("dnsrch=","dns_search=")
  591. # Get the state of the use_tcp flag.
  592. #
  593. def use_tcp?
  594. @config[:use_tcp]
  595. end
  596. alias_method :usevc, :use_tcp?
  597. alias_method :use_tcp, :use_tcp?
  598. # If +use_tcp+ is true, the resolver will perform all queries
  599. # using TCP virtual circuits instead of UDP datagrams, which
  600. # is the default for the DNS protocol.
  601. #
  602. # res.use_tcp = true
  603. # res.query "host.example.com"
  604. # #=> Sending TCP segments...
  605. #
  606. # Default is false.
  607. #
  608. def use_tcp=(bool)
  609. case bool
  610. when TrueClass,FalseClass
  611. @config[:use_tcp] = bool
  612. @logger.info("Use tcp flag changed to #{bool}")
  613. else
  614. raise ResolverArgumentError, "Argument must be boolean"
  615. end
  616. end
  617. alias usevc= use_tcp=
  618. def ignore_truncated?
  619. @config[:ignore_truncated]
  620. end
  621. alias_method :ignore_truncated, :ignore_truncated?
  622. def ignore_truncated=(bool)
  623. case bool
  624. when TrueClass,FalseClass
  625. @config[:ignore_truncated] = bool
  626. @logger.info("Ignore truncated flag changed to #{bool}")
  627. else
  628. raise ResolverArgumentError, "Argument must be boolean"
  629. end
  630. end
  631. # Return an object representing the value of the stored TCP
  632. # timeout the resolver will use in is queries. This object
  633. # is an instance of the class +TcpTimeout+, and two methods
  634. # are available for printing informations: TcpTimeout#to_s
  635. # and TcpTimeout#pretty_to_s.
  636. #
  637. # Here's some example:
  638. #
  639. # puts "Timeout of #{res.tcp_timeout} seconds" # implicit to_s
  640. # #=> Timeout of 150 seconds
  641. #
  642. # puts "You set a timeout of " + res.tcp_timeout.pretty_to_s
  643. # #=> You set a timeout of 2 minutes and 30 seconds
  644. #
  645. # If the timeout is infinite, a string "infinite" will
  646. # be returned.
  647. #
  648. def tcp_timeout
  649. @config[:tcp_timeout].to_s
  650. end
  651. # Set the value of TCP timeout for resolver queries that
  652. # will be performed using TCP. A value of 0 means that
  653. # the timeout will be infinite.
  654. # The value is stored internally as a +TcpTimeout+ object, see
  655. # the description for Resolver#tcp_timeout
  656. #
  657. # Default is 120 seconds
  658. def tcp_timeout=(secs)
  659. @config[:tcp_timeout] = TcpTimeout.new(secs)
  660. @logger.info("New TCP timeout value: #{@config[:tcp_timeout]} seconds")
  661. end
  662. # Return an object representing the value of the stored UDP
  663. # timeout the resolver will use in is queries. This object
  664. # is an instance of the class +UdpTimeout+, and two methods
  665. # are available for printing informations: UdpTimeout#to_s
  666. # and UdpTimeout#pretty_to_s.
  667. #
  668. # Here's some example:
  669. #
  670. # puts "Timeout of #{res.udp_timeout} seconds" # implicit to_s
  671. # #=> Timeout of 150 seconds
  672. #
  673. # puts "You set a timeout of " + res.udp_timeout.pretty_to_s
  674. # #=> You set a timeout of 2 minutes and 30 seconds
  675. #
  676. # If the timeout is zero, a string "not defined" will
  677. # be returned.
  678. #
  679. def udp_timeout
  680. @config[:udp_timeout].to_s
  681. end
  682. # Set the value of UDP timeout for resolver queries that
  683. # will be performed using UDP. A value of 0 means that
  684. # the timeout will not be used, and the resolver will use
  685. # only +retry_number+ and +retry_interval+ parameters.
  686. # That is the default.
  687. #
  688. # The value is stored internally as a +UdpTimeout+ object, see
  689. # the description for Resolver#udp_timeout
  690. #
  691. def udp_timeout=(secs)
  692. @config[:udp_timeout] = UdpTimeout.new(secs)
  693. @logger.info("New UDP timeout value: #{@config[:udp_timeout]} seconds")
  694. end
  695. # Set a new log file for the logger facility of the resolver
  696. # class. Could be a file descriptor too:
  697. #
  698. # res.log_file = $stderr
  699. #
  700. # Note that a new logging facility will be create, destroing
  701. # the old one, which will then be impossibile to recover.
  702. #
  703. def log_file=(log)
  704. @logger.close
  705. @config[:log_file] = log
  706. @logger = Logger.new(@config[:log_file])
  707. @logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
  708. end
  709. # This one permits to have a personal logger facility to handle
  710. # resolver messages, instead of new built-in one, which is set up
  711. # for a +$stdout+ (or +$stderr+) use.
  712. #
  713. # If you want your own logging facility you can create a new instance
  714. # of the +Logger+ class:
  715. #
  716. # log = Logger.new("/tmp/resolver.log","weekly",2*1024*1024)
  717. # log.level = Logger::DEBUG
  718. # log.progname = "ruby_resolver"
  719. #
  720. # and then pass it to the resolver:
  721. #
  722. # res.logger = log
  723. #
  724. # Note that this will destroy the precedent logger.
  725. #
  726. def logger=(logger)
  727. if logger.kind_of? Logger
  728. @logger.close
  729. @logger = logger
  730. else
  731. raise ResolverArgumentError, "Argument must be an instance of Logger class"
  732. end
  733. end
  734. # Set the log level for the built-in logging facility.
  735. #
  736. # The log level can be one of the following:
  737. #
  738. # - +Net::DNS::DEBUG+
  739. # - +Net::DNS::INFO+
  740. # - +Net::DNS::WARN+
  741. # - +Net::DNS::ERROR+
  742. # - +Net::DNS::FATAL+
  743. #
  744. # Note that if the global variable $DEBUG is set (like when the
  745. # -d switch is used at the command line) the logger level is
  746. # automatically set at DEGUB.
  747. #
  748. # For further informations, see Logger documentation in the
  749. # Ruby standard library.
  750. #
  751. def log_level=(level)
  752. @logger.level = level
  753. end
  754. # Performs a DNS query for the given name, applying the searchlist if
  755. # appropriate. The search algorithm is as follows:
  756. #
  757. # 1. If the name contains at least one dot, try it as is.
  758. # 2. If the name doesn't end in a dot then append each item in the search
  759. # list to the name. This is only done if +dns_search+ is true.
  760. # 3. If the name doesn't contain any dots, try it as is.
  761. #
  762. # The record type and class can be omitted; they default to +A+ and +IN+.
  763. #
  764. # packet = res.search('mailhost')
  765. # packet = res.search('mailhost.example.com')
  766. # packet = res.search('example.com', Net::DNS::MX)
  767. # packet = res.search('user.passwd.example.com', Net::DNS::TXT, Net::DNS::HS)
  768. #
  769. # If the name is an IP address (Ipv4 or IPv6), in the form of a string
  770. # or a +IPAddr+ object, then an appropriate PTR query will be performed:
  771. #
  772. # ip = IPAddr.new("172.16.100.2")
  773. # packet = res.search(ip)
  774. # packet = res.search("192.168.10.254")
  775. #
  776. # Returns a Net::DNS::Packet object. If you need to examine the response packet
  777. # whether it contains any answers or not, use the send() method instead.
  778. #
  779. def search(name,type=Net::DNS::A,cls=Net::DNS::IN)
  780. # If the name contains at least one dot then try it as is first.
  781. if name.include? "."
  782. @logger.debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
  783. ans = query(name,type,cls)
  784. return ans if ans.header.anCount > 0
  785. end
  786. # If the name doesn't end in a dot then apply the search list.
  787. if name !~ /\.$/ and @config[:dns_search]
  788. @config[:searchlist].each do |domain|
  789. newname = name + "." + domain
  790. @logger.debug "Search(#{newname},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
  791. ans = query(newname,type,cls)
  792. return ans if ans.header.anCount > 0
  793. end
  794. end
  795. # Finally, if the name has no dots then try it as is.
  796. @logger.debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
  797. query(name+".",type,cls)
  798. end
  799. # Performs a DNS query for the given name; the search list
  800. # is not applied. If the name doesn't contain any dots and
  801. # +defname+ is true then the default domain will be appended.
  802. #
  803. # The record type and class can be omitted; they default to +A+
  804. # and +IN+. If the name looks like an IP address (IPv4 or IPv6),
  805. # then an appropriate PTR query will be performed.
  806. #
  807. # packet = res.query('mailhost')
  808. # packet = res.query('mailhost.example.com')
  809. # packet = res.query('example.com', Net::DNS::MX)
  810. # packet = res.query('user.passwd.example.com', Net::DNS::TXT, Net::DNS::HS)
  811. #
  812. # If the name is an IP address (Ipv4 or IPv6), in the form of a string
  813. # or a +IPAddr+ object, then an appropriate PTR query will be performed:
  814. #
  815. # ip = IPAddr.new("172.16.100.2")
  816. # packet = res.query(ip)
  817. # packet = res.query("192.168.10.254")
  818. #
  819. # Returns a Net::DNS::Packet object. If you need to examine the response
  820. # packet whether it contains any answers or not, use the Resolver#send
  821. # method instead.
  822. #
  823. def query(name,type=Net::DNS::A,cls=Net::DNS::IN)
  824. # If the name doesn't contain any dots then append the default domain.
  825. if name !~ /\./ and name !~ /:/ and @config[:defnames]
  826. name += "." + @config[:domain]
  827. end
  828. @logger.debug "Query(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
  829. send(name,type,cls)
  830. end
  831. # Performs a DNS query for the given name. Neither the
  832. # searchlist nor the default domain will be appended.
  833. #
  834. # The argument list can be either a Net::DNS::Packet object
  835. # or a name string plus optional type and class, which if
  836. # omitted default to +A+ and +IN+.
  837. #
  838. # Returns a Net::DNS::Packet object.
  839. #
  840. # # Sending a +Packet+ object
  841. # send_packet = Net::DNS::Packet.new("host.example.com",Net::DNS::NS,Net::DNS::HS)
  842. # packet = res.send(send_packet)
  843. #
  844. # # Performing a query
  845. # packet = res.send("host.example.com")
  846. # packet = res.send("host.example.com",Net::DNS::NS)
  847. # packet = res.send("host.example.com",Net::DNS::NS,Net::DNS::HS)
  848. #
  849. # If the name is an IP address (Ipv4 or IPv6), in the form of a string
  850. # or a IPAddr object, then an appropriate PTR query will be performed:
  851. #
  852. # ip = IPAddr.new("172.16.100.2")
  853. # packet = res.send(ip)
  854. # packet = res.send("192.168.10.254")
  855. #
  856. # Use +packet.header.ancount+ or +packet.answer+ to find out if there
  857. # were any records in the answer section.
  858. #
  859. def send(argument,type=Net::DNS::A,cls=Net::DNS::IN)
  860. if @config[:nameservers].size == 0
  861. raise ResolverError, "No nameservers specified!"
  862. end
  863. method = :send_udp
  864. if argument.kind_of? Net::DNS::Packet
  865. packet = argument
  866. else
  867. packet = make_query_packet(argument,type,cls)
  868. end
  869. # Store packet_data for performance improvements,
  870. # so methods don't keep on calling Packet#data
  871. packet_data = packet.data
  872. packet_size = packet_data.size
  873. # Choose whether use TCP, UDP or RAW
  874. if packet_size > @config[:packet_size] # Must use TCP, either plain or raw
  875. if @raw # Use raw sockets?
  876. @logger.info "Sending #{packet_size} bytes using TCP over RAW socket"
  877. method = :send_raw_tcp
  878. else
  879. @logger.info "Sending #{packet_size} bytes using TCP"
  880. method = :send_tcp
  881. end
  882. else # Packet size is inside the boundaries
  883. if @raw # Use raw sockets?
  884. @logger.info "Sending #{packet_size} bytes using UDP over RAW socket"
  885. method = :send_raw_udp
  886. elsif use_tcp? # User requested TCP
  887. @logger.info "Sending #{packet_size} bytes using TCP"
  888. method = :send_tcp
  889. else # Finally use UDP
  890. @logger.info "Sending #{packet_size} bytes using UDP"
  891. end
  892. end
  893. if type == Net::DNS::AXFR
  894. if @raw
  895. @logger.warn "AXFR query, switching to TCP over RAW socket"
  896. method = :send_raw_tcp
  897. else
  898. @logger.warn "AXFR query, switching to TCP"
  899. method = :send_tcp
  900. end
  901. end
  902. ans = self.old_send(method,packet,packet_data)
  903. unless ans
  904. @logger.fatal "No response from nameservers list: aborting"
  905. raise NoResponseError
  906. end
  907. @logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}"
  908. response = Net::DNS::Packet.parse(ans[0],ans[1])
  909. if response.header.truncated? and not ignore_truncated?
  910. @logger.warn "Packet truncated, retrying using TCP"
  911. self.use_tcp = true
  912. begin
  913. return send(argument,type,cls)
  914. ensure
  915. self.use_tcp = false
  916. end
  917. end
  918. return response
  919. end
  920. #
  921. # Performs a zone transfer for the zone passed as a parameter.
  922. #
  923. # Returns a list of Net::DNS::Packet (not answers!)
  924. #
  925. def axfr(name,cls=Net::DNS::IN)
  926. @logger.info "Requested AXFR transfer, zone #{name} class #{cls}"
  927. if @config[:nameservers].size == 0
  928. raise ResolverError, "No nameservers specified!"
  929. end
  930. method = :send_tcp
  931. packet = make_query_packet(name, Net::DNS::AXFR, cls)
  932. # Store packet_data for performance improvements,
  933. # so methods don't keep on calling Packet#data
  934. packet_data = packet.data
  935. packet_size = packet_data.size
  936. if @raw
  937. @logger.warn "AXFR query, switching to TCP over RAW socket"
  938. method = :send_raw_tcp
  939. else
  940. @logger.warn "AXFR query, switching to TCP"
  941. method = :send_tcp
  942. end
  943. answers = []
  944. soa = 0
  945. self.old_send(method, packet, packet_data) do |ans|
  946. @logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}"
  947. begin
  948. response = Net::DNS::Packet.parse(ans[0],ans[1])
  949. if response.answer[0].type == "SOA"
  950. soa += 1
  951. if soa >= 2
  952. break
  953. end
  954. end
  955. answers << response
  956. rescue NameError => e
  957. @logger.warn "Error parsing axfr response: #{e.message}"
  958. end
  959. end
  960. if answers.empty?
  961. @logger.fatal "No response from nameservers list: aborting"
  962. raise NoResponseError
  963. end
  964. return answers
  965. end
  966. #
  967. # Performs an MX query for the domain name passed as parameter.
  968. #
  969. # It actually uses the same methods a normal Resolver query would
  970. # use, but automatically sort the results based on preferences
  971. # and returns an ordered array.
  972. #
  973. # Example:
  974. #
  975. # res = Net::DNS::Resolver.new
  976. # res.mx("google.com")
  977. #
  978. def mx(name,cls=Net::DNS::IN)
  979. arr = []
  980. send(name, Net::DNS::MX, cls).answer.each do |entry|
  981. arr << entry if entry.type == 'MX'
  982. end
  983. return arr.sort_by {|a| a.preference}
  984. end
  985. private
  986. # Parse a configuration file specified as the argument.
  987. #
  988. def parse_config_file
  989. if RUBY_PLATFORM =~ /mswin32|cygwin|mingw|bccwin/
  990. require 'win32/resolv'
  991. arr = Win32::Resolv.get_resolv_info
  992. self.domain = arr[0]
  993. self.nameservers = arr[1]
  994. else
  995. IO.foreach(@config[:config_file]) do |line|
  996. line.gsub!(/\s*[;#].*/,"")
  997. next unless line =~ /\S/
  998. case line
  999. when /^\s*domain\s+(\S+)/
  1000. self.domain = $1
  1001. when /^\s*search\s+(.*)/
  1002. self.searchlist = $1.split(" ")
  1003. when /^\s*nameserver\s+(.*)/
  1004. self.nameservers = $1.split(" ")
  1005. end
  1006. end
  1007. end
  1008. end
  1009. # Parse environment variables
  1010. def parse_environment_variables
  1011. if ENV['RES_NAMESERVERS']
  1012. self.nameservers = ENV['RES_NAMESERVERS'].split(" ")
  1013. end
  1014. if ENV['RES_SEARCHLIST']
  1015. self.searchlist = ENV['RES_SEARCHLIST'].split(" ")
  1016. end
  1017. if ENV['LOCALDOMAIN']
  1018. self.domain = ENV['LOCALDOMAIN']
  1019. end
  1020. if ENV['RES_OPTIONS']
  1021. ENV['RES_OPTIONS'].split(" ").each do |opt|
  1022. name,val = opt.split(":")
  1023. begin
  1024. eval("self.#{name} = #{val}")
  1025. rescue NoMethodError
  1026. raise ResolverArgumentError, "Invalid ENV option #{name}"
  1027. end
  1028. end
  1029. end
  1030. end
  1031. def nameservers_from_name(arg)
  1032. arr = []
  1033. arg.split(" ").each do |name|
  1034. Resolver.new.search(name).each_address do |ip|
  1035. arr << ip
  1036. end
  1037. end
  1038. @config[:nameservers] << arr
  1039. end
  1040. def make_query_packet(string,type,cls)
  1041. case string
  1042. when IPAddr
  1043. name = string.reverse
  1044. type = Net::DNS::PTR
  1045. @logger.warn "PTR query required for address #{string}, changing type to PTR"
  1046. when /\d/ # Contains a number, try to see if it's an IP or IPv6 address
  1047. begin
  1048. name = IPAddr.new(string).reverse
  1049. type = Net::DNS::PTR
  1050. rescue ArgumentError
  1051. name = string if valid? string
  1052. end
  1053. else
  1054. name = string if valid? string
  1055. end
  1056. # Create the packet
  1057. packet = Net::DNS::Packet.new(name,type,cls)
  1058. if packet.query?
  1059. packet.header.recursive = @config[:recursive] ? 1 : 0
  1060. end
  1061. # DNSSEC and TSIG stuff to be inserted here
  1062. packet
  1063. end
  1064. def send_tcp(packet,packet_data)
  1065. ans = nil
  1066. length = [packet_data.size].pack("n")
  1067. @config[:nameservers].each do |ns|
  1068. begin
  1069. socket = Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)
  1070. socket.bind(Socket.pack_sockaddr_in(@config[:source_port],@config[:source_address].to_s))
  1071. sockaddr = Socket.pack_sockaddr_in(@config[:port],ns.to_s)
  1072. @config[:tcp_timeout].timeout do
  1073. catch "next nameserver" do
  1074. socket.connect(sockaddr)
  1075. @logger.info "Contacting nameserver #{ns} port #{@config[:port]}"
  1076. socket.write(length+packet_data)
  1077. got_something = false
  1078. loop do
  1079. buffer = ""
  1080. ans = socket.recv(Net::DNS::INT16SZ)
  1081. if ans.size == 0
  1082. if got_something
  1083. break #Proper exit from loop
  1084. else
  1085. @logger.warn "Connection reset to nameserver #{ns}, trying next."
  1086. throw "next nameserver"
  1087. end
  1088. end
  1089. got_something = true
  1090. len = ans.unpack("n")[0]
  1091. @logger.info "Receiving #{len} bytes..."
  1092. if len == 0
  1093. @logger.warn "Receiving 0 length packet from nameserver #{ns}, trying next."
  1094. throw "next nameserver"
  1095. end
  1096. while (buffer.size < len)
  1097. left = len - buffer.size
  1098. temp,from = socket.recvfrom(left)
  1099. buffer += temp
  1100. end
  1101. unless buffer.size == len
  1102. @logger.warn "Malformed packet from nameserver #{ns}, trying next."
  1103. throw "next nameserver"
  1104. end
  1105. if block_given?
  1106. yield [buffer,["",@config[:port],ns.to_s,ns.to_s]]
  1107. else
  1108. return [buffer,["",@config[:port],ns.to_s,ns.to_s]]
  1109. end
  1110. end
  1111. end
  1112. end
  1113. rescue Timeout::Error
  1114. @logger.warn "Nameserver #{ns} not responding within TCP timeout, trying next one"
  1115. next
  1116. ensure
  1117. socket.close
  1118. end
  1119. end
  1120. return nil
  1121. end
  1122. def send_udp(packet,packet_data)
  1123. socket = UDPSocket.new
  1124. socket.bind(@config[:source_address].to_s,@config[:source_port])
  1125. ans = nil
  1126. response = ""
  1127. @config[:nameservers].each do |ns|
  1128. begin
  1129. @config[:udp_timeout].timeout do
  1130. @logger.info "Contacting nameserver #{ns} port #{@config[:port]}"
  1131. socket.send(packet_data,0,ns.to_s,@config[:port])
  1132. ans = socket.recvfrom(@config[:packet_size])
  1133. end
  1134. break if ans
  1135. rescue Timeout::Error
  1136. @logger.warn "Nameserver #{ns} not responding within UDP timeout, trying next one"
  1137. next
  1138. end
  1139. end
  1140. ans
  1141. end
  1142. def valid?(name)
  1143. if name =~ /[^-\w\.]/
  1144. raise ResolverArgumentError, "Invalid domain name #{name}"
  1145. else
  1146. true
  1147. end
  1148. end
  1149. end # class Resolver
  1150. end # module DNS
  1151. end # module Net
  1152. class ResolverError < ArgumentError # :nodoc:
  1153. end
  1154. class ResolverArgumentError < ArgumentError # :nodoc:
  1155. end
  1156. class NoResponseError < StandardError # :nodoc:
  1157. end
  1158. module ExtendHash # :nodoc:
  1159. # Returns an hash with all the
  1160. # keys turned into downcase
  1161. #
  1162. # hsh = {"Test" => 1, "FooBar" => 2}
  1163. # hsh.key_downcase!
  1164. # #=> {"test"=>1,"foobar"=>2}
  1165. #
  1166. def key_downcase!
  1167. hsh = Hash.new
  1168. self.each do |key,val|
  1169. hsh[key.downcase] = val
  1170. end
  1171. self.replace(hsh)
  1172. end
  1173. end
  1174. class Hash # :nodoc:
  1175. include ExtendHash
  1176. end