PageRenderTime 57ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/net/dns/resolver.rb

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