Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/proto/dns/resolver.rb
Views: 11704
# -*- coding: binary -*-12require 'net/dns/resolver'3require 'dnsruby'45module Rex6module Proto7module DNS89##10# Provides Rex::Sockets compatible version of Net::DNS::Resolver11# Modified to work with Dnsruby::Messages, their resolvers are too heavy12##13class Resolver < Net::DNS::Resolver1415Defaults = {16:config_file => nil,17:log_file => File::NULL, # formerly $stdout, should be tied in with our loggers18:port => 53,19:searchlist => [],20:nameservers => [],21:domain => "",22:source_port => 0,23:source_address => IPAddr.new("0.0.0.0"),24:retry_interval => 5,25:retry_number => 4,26:recursive => true,27:defname => true,28:dns_search => true,29:use_tcp => false,30:ignore_truncated => false,31:packet_size => 512,32:tcp_timeout => TcpTimeout.new(5),33:udp_timeout => UdpTimeout.new(5),34:context => {},35:comm => nil,36:static_hosts => {}37}3839attr_accessor :context, :comm, :static_hostnames40#41# Provide override for initializer to use local Defaults constant42#43# @param config [Hash] Configuration options as consumed by parent class44def initialize(config = {})45raise ResolverArgumentError, "Argument has to be Hash" unless config.kind_of? Hash46# config.key_downcase!47@config = Defaults.merge config48@config[:config_file] ||= self.class.default_config_file49@raw = false50# New logger facility51@logger = Logger.new(@config[:log_file])52@logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN5354#------------------------------------------------------------55# Resolver configuration will be set in order from:56# 1) initialize arguments57# 2) ENV variables58# 3) config file59# 4) defaults (and /etc/resolv.conf for config)60#------------------------------------------------------------6162#------------------------------------------------------------63# Parsing config file64#------------------------------------------------------------65parse_config_file6667#------------------------------------------------------------68# Parsing ENV variables69#------------------------------------------------------------70parse_environment_variables7172#------------------------------------------------------------73# Parsing arguments74#------------------------------------------------------------75comm = config.delete(:comm)76context = config.delete(:context)77static_hosts = config.delete(:static_hosts)78config.each do |key,val|79next if key == :log_file or key == :config_file80begin81eval "self.#{key.to_s} = val"82rescue NoMethodError83raise ResolverArgumentError, "Option #{key} not valid"84end85end8687self.static_hostnames = StaticHostnames.new(hostnames: static_hosts)88begin89self.static_hostnames.parse_hosts_file90rescue StandardError => e91@logger.error 'Failed to parse the hosts file, ignoring it'92# if the hosts file is corrupted, just use a default instance with any specified hostnames93self.static_hostnames = StaticHostnames.new(hostnames: static_hosts)94end95end96#97# Provides current proxy setting if configured98#99# @return [String] Current proxy configuration100def proxies101@config[:proxies].inspect if @config[:proxies]102end103104#105# Configure proxy setting and additional timeout106#107# @param prox [String] SOCKS proxy connection string108# @param timeout_added [Fixnum] Added TCP timeout to account for proxy109def proxies=(prox, timeout_added = 250)110return if prox.nil?111if prox.is_a?(String) and prox.strip =~ /^socks/i112@config[:proxies] = prox.strip113@config[:use_tcp] = true114self.tcp_timeout = self.tcp_timeout.to_s.to_i + timeout_added115@logger.info "SOCKS proxy set, using TCP, increasing timeout"116else117raise ResolverError, "Only socks proxies supported"118end119end120121#122# Find the nameservers to use for a given DNS request123# @param _dns_message [Dnsruby::Message] The DNS message to be sent124#125# @return [Array<Array>] A list of nameservers, each with Rex::Socket options126#127def upstream_resolvers_for_packet(_dns_message)128@config[:nameservers].map do |ns|129UpstreamResolver.create_dns_server(ns.to_s)130end131end132133def upstream_resolvers_for_query(name, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)134name, type, cls = preprocess_query_arguments(name, type, cls)135net_packet = make_query_packet(name, type, cls)136# This returns a Net::DNS::Packet. Convert to Dnsruby::Message for consistency137packet = Rex::Proto::DNS::Packet.encode_drb(net_packet)138upstream_resolvers_for_packet(packet)139end140141#142# Send DNS request over appropriate transport and process response143#144# @param argument [Object] An object holding the DNS message to be processed.145# @param type [Fixnum] Type of record to look up146# @param cls [Fixnum] Class of question to look up147# @return [Dnsruby::Message] DNS response148#149def send(argument, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)150case argument151when Dnsruby::Message152packet = argument153when Net::DNS::Packet, Resolv::DNS::Message154packet = Rex::Proto::DNS::Packet.encode_drb(argument)155else156net_packet = make_query_packet(argument,type,cls)157# This returns a Net::DNS::Packet. Convert to Dnsruby::Message for consistency158packet = Rex::Proto::DNS::Packet.encode_drb(net_packet)159end160161162upstream_resolvers = upstream_resolvers_for_packet(packet)163if upstream_resolvers.empty?164raise ResolverError, "No upstream resolvers specified!"165end166167ans = nil168upstream_resolvers.each do |upstream_resolver|169case upstream_resolver.type170when UpstreamResolver::Type::BLACK_HOLE171ans = resolve_via_black_hole(upstream_resolver, packet, type, cls)172when UpstreamResolver::Type::DNS_SERVER173ans = resolve_via_dns_server(upstream_resolver, packet, type, cls)174when UpstreamResolver::Type::STATIC175ans = resolve_via_static(upstream_resolver, packet, type, cls)176when UpstreamResolver::Type::SYSTEM177ans = resolve_via_system(upstream_resolver, packet, type, cls)178end179180break if (ans and ans[0].length > 0)181end182183unless (ans and ans[0].length > 0)184@logger.fatal "No response from upstream resolvers: aborting"185raise NoResponseError186end187188# response = Net::DNS::Packet.parse(ans[0],ans[1])189response = Dnsruby::Message.decode(ans[0])190191if response.header.tc and not ignore_truncated?192@logger.warn "Packet truncated, retrying using TCP"193self.use_tcp = true194begin195return send(argument,type,cls)196ensure197self.use_tcp = false198end199end200201response202end203204#205# Send request over TCP206#207# @param packet [Net::DNS::Packet] Packet associated with packet_data208# @param packet_data [String] Data segment of DNS request packet209# @param nameservers [Array<[String,Hash]>] List of nameservers to use for this request, and their associated socket options210# @param prox [String] Proxy configuration for TCP socket211#212# @return ans [String] Raw DNS reply213def send_tcp(packet, packet_data, nameservers, prox = @config[:proxies])214ans = nil215length = [packet_data.size].pack("n")216nameservers.each do |ns, socket_options|217socket = nil218config = {219'PeerHost' => ns.to_s,220'PeerPort' => @config[:port].to_i,221'Proxies' => prox,222'Context' => @config[:context],223'Comm' => @config[:comm],224'Timeout' => @config[:tcp_timeout]225}226config.update(socket_options)227unless config['Comm'].nil? || config['Comm'].alive?228@logger.warn("Session #{config['Comm'].sid} not active, and cannot be used to resolve DNS")229next230end231232suffix = " over session #{@config['Comm'].sid}" unless @config['Comm'].nil?233if @config[:source_port] > 0234config['LocalPort'] = @config[:source_port]235end236if @config[:source_host].to_s != '0.0.0.0'237config['LocalHost'] = @config[:source_host] unless @config[:source_host].nil?238end239begin240suffix = ''241begin242socket = Rex::Socket::Tcp.create(config)243rescue244@logger.warn "TCP Socket could not be established to #{ns}:#{@config[:port]} #{@config[:proxies]}#{suffix}"245next246end247next unless socket #248@logger.info "Contacting nameserver #{ns} port #{@config[:port]}#{suffix}"249socket.write(length+packet_data)250got_something = false251loop do252buffer = ""253attempts = 3254begin255ans = socket.recv(2)256rescue Errno::ECONNRESET257@logger.warn "TCP Socket got Errno::ECONNRESET from #{ns}:#{@config[:port]} #{@config[:proxies]}#{suffix}"258attempts -= 1259retry if attempts > 0260end261if ans.size == 0262if got_something263break #Proper exit from loop264else265@logger.warn "Connection reset to nameserver #{ns}#{suffix}, trying next."266throw :next_ns267end268end269got_something = true270len = ans.unpack("n")[0]271272@logger.info "Receiving #{len} bytes..."273274if len.nil? or len == 0275@logger.warn "Receiving 0 length packet from nameserver #{ns}#{suffix}, trying next."276throw :next_ns277end278279while (buffer.size < len)280left = len - buffer.size281temp,from = socket.recvfrom(left)282buffer += temp283end284285unless buffer.size == len286@logger.warn "Malformed packet from nameserver #{ns}#{suffix}, trying next."287throw :next_ns288end289if block_given?290yield [buffer,["",@config[:port],ns.to_s,ns.to_s]]291else292return [buffer,["",@config[:port],ns.to_s,ns.to_s]]293end294end295rescue Timeout::Error296@logger.warn "Nameserver #{ns}#{suffix} not responding within TCP timeout, trying next one"297next298ensure299socket.close if socket300end301end302return nil303end304305#306# Send request over UDP307#308# @param packet [Net::DNS::Packet] Packet associated with packet_data309# @param packet_data [String] Data segment of DNS request packet310# @param nameservers [Array<[String,Hash]>] List of nameservers to use for this request, and their associated socket options311#312# @return ans [String] Raw DNS reply313def send_udp(packet,packet_data, nameservers)314ans = nil315nameservers.each do |ns, socket_options|316begin317config = {318'PeerHost' => ns.to_s,319'PeerPort' => @config[:port].to_i,320'Context' => @config[:context],321'Comm' => @config[:comm],322'Timeout' => @config[:udp_timeout]323}324config.update(socket_options)325unless config['Comm'].nil? || config['Comm'].alive?326@logger.warn("Session #{config['Comm'].sid} not active, and cannot be used to resolve DNS")327next328end329330if @config[:source_port] > 0331config['LocalPort'] = @config[:source_port]332end333if @config[:source_host] != IPAddr.new('0.0.0.0')334config['LocalHost'] = @config[:source_host] unless @config[:source_host].nil?335end336socket = Rex::Socket::Udp.create(config)337rescue338@logger.warn "UDP Socket could not be established to #{ns}:#{@config[:port]}"339next340end341@logger.info "Contacting nameserver #{ns} port #{@config[:port]}"342#socket.sendto(packet_data, ns.to_s, @config[:port].to_i, 0)343socket.write(packet_data)344ans = socket.recvfrom(@config[:packet_size])345break if ans346rescue Timeout::Error347@logger.warn "Nameserver #{ns} not responding within UDP timeout, trying next one"348next349end350ans351end352353354#355# Perform search using the configured searchlist and resolvers356#357# @param name358# @param type [Fixnum] Type of record to look up359# @param cls [Fixnum] Class of question to look up360#361# @return ans [Dnsruby::Message] DNS Response362def search(name, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)363return query(name,type,cls) if name.class == IPAddr364# If the name contains at least one dot then try it as is first.365if name.include? "."366@logger.debug "Search(#{name},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"367ans = query(name,type,cls)368return ans if ans.header.ancount > 0369end370# If the name doesn't end in a dot then apply the search list.371if name !~ /\.$/ and @config[:dns_search]372@config[:searchlist].each do |domain|373newname = name + "." + domain374@logger.debug "Search(#{newname},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"375ans = query(newname,type,cls)376return ans if ans.header.ancount > 0377end378end379# Finally, if the name has no dots then try it as is.380@logger.debug "Search(#{name},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"381return query(name+".",type,cls)382end383384#385# Perform query with default domain validation386#387# @param name388# @param type [Fixnum] Type of record to look up389# @param cls [Fixnum] Class of question to look up390#391# @return ans [Dnsruby::Message] DNS Response392def query(name, type = Dnsruby::Types::A, cls = Dnsruby::Classes::IN)393name, type, cls = preprocess_query_arguments(name, type, cls)394@logger.debug "Query(#{name},#{Dnsruby::Types.new(type)},#{Dnsruby::Classes.new(cls)})"395send(name,type,cls)396end397398def self.default_config_file399%w[400/etc/resolv.conf401/data/data/com.termux/files/usr/etc/resolv.conf402].find do |path|403File.file?(path) && File.readable?(path)404end405end406407private408409def preprocess_query_arguments(name, type, cls)410return [name, type, cls] if name.class == IPAddr411412# If the name doesn't contain any dots then append the default domain.413if name !~ /\./ and name !~ /:/ and @config[:defname]414name += "." + @config[:domain]415end416[name, type, cls]417end418419def resolve_via_dns_server(upstream_resolver, packet, type, _cls)420method = self.use_tcp? ? :send_tcp : :send_udp421422# Store packet_data for performance improvements,423# so methods don't keep on calling Packet#encode424packet_data = packet.encode425packet_size = packet_data.size426427# Choose whether use TCP, UDP428if packet_size > @config[:packet_size] # Must use TCP429@logger.info "Sending #{packet_size} bytes using TCP due to size"430method = :send_tcp431else # Packet size is inside the boundaries432if use_tcp? or !(proxies.nil? or proxies.empty?) # User requested TCP433@logger.info "Sending #{packet_size} bytes using TCP due to tcp flag"434method = :send_tcp435elsif !supports_udp?(upstream_resolver)436@logger.info "Sending #{packet_size} bytes using TCP due to the presence of a non-UDP-compatible comm channel"437method = :send_tcp438else # Finally use UDP439@logger.info "Sending #{packet_size} bytes using UDP"440method = :send_udp unless method == :send_tcp441end442end443444if type == Dnsruby::Types::AXFR445@logger.warn "AXFR query, switching to TCP" unless method == :send_tcp446method = :send_tcp447end448449nameserver = [upstream_resolver.destination, upstream_resolver.socket_options]450ans = self.__send__(method, packet, packet_data, [nameserver])451452if (ans and ans[0].length > 0)453@logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}"454end455456ans457end458459def resolve_via_black_hole(upstream_resolver, packet, type, cls)460# do not just return nil because that will cause the next resolver to be used461@logger.info "No response from upstream resolvers: black-hole"462raise NoResponseError463end464465def resolve_via_static(upstream_resolver, packet, type, cls)466simple_name_lookup(upstream_resolver, packet, type, cls) do |name, _family|467static_hostnames.get(name, type)468end469end470471def resolve_via_system(upstream_resolver, packet, type, cls)472# This system resolver will use host operating systems `getaddrinfo` (or equivalent function) to perform name473# resolution. This is primarily useful if that functionality is hooked or modified by an external application such474# as proxychains. This handler though can only process A and AAAA requests.475simple_name_lookup(upstream_resolver, packet, type, cls) do |name, family|476addrinfos = ::Addrinfo.getaddrinfo(name, 0, family, ::Socket::SOCK_STREAM)477addrinfos.map(&:ip_address)478end479end480481def simple_name_lookup(upstream_resolver, packet, type, cls, &block)482return nil unless cls == Dnsruby::Classes::IN483484# todo: make sure this will work if the packet has multiple questions, figure out how that's handled485name = packet.question.first.qname.to_s486case type487when Dnsruby::Types::A488family = ::Socket::AF_INET489when Dnsruby::Types::AAAA490family = ::Socket::AF_INET6491else492return nil493end494495ip_addresses = nil496begin497ip_addresses = block.call(name, family)498rescue StandardError => e499@logger.error("The #{upstream_resolver.type} name lookup block failed for #{name}")500end501return nil unless ip_addresses && !ip_addresses.empty?502503message = Dnsruby::Message.new504message.add_question(name, type, cls)505ip_addresses.each do |ip_address|506message.add_answer(Dnsruby::RR.new_from_hash(507name: name,508type: type,509ttl: 0,510address: ip_address.to_s511))512end513[message.encode]514end515516def supports_udp?(upstream_resolver)517return false unless upstream_resolver.type == UpstreamResolver::Type::DNS_SERVER518519comm = upstream_resolver.socket_options.fetch('Comm') { @config[:comm] || Rex::Socket::SwitchBoard.best_comm(upstream_resolver.destination) }520return false if comm && !comm.supports_udp?521522true523end524end # Resolver525526end527end528end529530531