Path: blob/master/lib/rex/proto/dns/server.rb
19591 views
# -*- coding: binary -*-12require 'rex/socket'34module Rex5module Proto6module DNS78class Server910class MockDnsClient11extend Forwardable12attr_reader :peerhost, :peerport, :srvsock1314def_delegators :@srvsock, :localhost, :localport, :sendto1516#17# Create mock DNS client18#19# @param host [String] PeerHost IP address20# @param port [Fixnum] PeerPort integer21def initialize(host, port, sock)22@peerhost = host23@peerport = port24@srvsock = sock25end2627#28# Test method to prevent GC/ObjectSpace abuse via class lookups29#30def mock_dns_client?31true32end3334def write(data)35srvsock.sendto(data, peerhost, peerport)36end37end3839include Rex::IO::GramServer4041Packet = Rex::Proto::DNS::Packet42#43# Create DNS Server44#45# @param lhost [String] Listener address46# @param lport [Fixnum] Listener port47# @param udp [TrueClass, FalseClass] Listen on UDP socket48# @param tcp [TrueClass, FalseClass] Listen on TCP socket49# @param res [Rex::Proto::DNS::Resolver] Resolver to use, nil to create a fresh one50# @param ctx [Hash] Framework context for sockets51# @param dblock [Proc] Handler for :dispatch_request flow control interception52# @param sblock [Proc] Handler for :send_response flow control interception53#54# @return [Rex::Proto::DNS::Server] DNS Server object55attr_accessor :serve_tcp, :serve_udp, :fwd_res, :cache, :start_cache56attr_reader :serve_udp, :serve_tcp, :sock_options, :lock, :udp_sock, :tcp_sock57def initialize(lhost = '0.0.0.0', lport = 53, udp = true, tcp = false, start_cache = true, res = nil, comm = nil, ctx = {}, dblock = nil, sblock = nil)5859@serve_udp = udp60@serve_tcp = tcp61@sock_options = {62'LocalHost' => lhost,63'LocalPort' => lport,64'Context' => ctx,65'Comm' => comm66}67self.fwd_res = res.nil? ? Rex::Proto::DNS::Resolver.new(:comm => comm, :context => ctx) : res68self.listener_thread = nil69self.dispatch_request_proc = dblock70self.send_response_proc = sblock71self.start_cache = start_cache72self.cache = Rex::Proto::DNS::Cache.new73@lock = Mutex.new74end7576#77# Switch DNS forwarders in resolver with thread safety78#79# @param ns [Array, String] List of (or single) nameservers to use80def switchns(ns = [])81if ns.respond_to?(:split)82ns = [ns]83end84self.lock.synchronize do85self.fwd_res.nameserver = ns86end87end8889#90# Check if server is running91#92def running?93self.listener_thread and self.listener_thread.alive?94end9596#97# Start the DNS server and cache98# @param start_cache [TrueClass, FalseClass] stop the cache99def start100101if self.serve_udp102@udp_sock = Rex::Socket::Udp.create(self.sock_options)103self.listener_thread = Rex::ThreadFactory.spawn("UDPDNSServerListener", false) {104monitor_listener105}106end107108if self.serve_tcp109@tcp_sock = Rex::Socket::TcpServer.create(self.sock_options)110self.tcp_sock.on_client_data_proc = Proc.new { |cli|111on_client_data(cli)112}113self.tcp_sock.start114if !self.serve_udp115self.listener_thread = tcp_sock.listener_thread116end117end118119self.cache.start if self.start_cache120end121122#123# Stop the DNS server and cache124#125# @param flush_cache [TrueClass,FalseClass] Flush eDNS cache on stop126def stop(flush_cache = false)127ensure_close = [self.udp_sock, self.tcp_sock].compact128begin129self.listener_thread.kill if self.listener_thread.respond_to?(:kill)130self.listener_thread = nil131ensure132while csock = ensure_close.shift133csock.stop if csock.respond_to?(:stop)134csock.close unless csock.respond_to?(:close) and csock.closed?135end136end137self.cache.stop(flush_cache)138end139140#141# Process client request, handled with dispatch_request_proc if set142#143# @param cli [Rex::Socket::Tcp, Rex::Socket::Udp] Client sending the request144# @param data [String] raw DNS request data145def dispatch_request(cli, data)146if self.dispatch_request_proc147self.dispatch_request_proc.call(cli,data)148else149default_dispatch_request(cli,data)150end151end152153#154# Default DNS request dispatcher, attempts to find155# response records in cache or forwards request upstream156#157# @param cli [Rex::Socket::Tcp, Rex::Socket::Udp] Client sending the request158# @param data [String] raw DNS request data159def default_dispatch_request(cli,data)160return if data.strip.empty?161req = Packet.encode_drb(data)162forward = req.dup163# Find cached items, remove request from forwarded packet164req.question.each do |ques|165cached = self.cache.find(ques.qname, ques.qtype)166if cached.empty?167next168else169req.instance_variable_set(:@answer, (req.answer + cached).uniq)170forward.question.delete(ques)171end172end173# Forward remaining requests, cache responses174if forward.question.count > 0 and @fwd_res175forwarded = self.fwd_res.send(forward)176req.instance_variable_set(:@answer, (req.answer + forwarded.answer).uniq)177forwarded.answer.each do |ans|178self.cache.cache_record(ans)179end180req.header.ra = true # Set recursion bit181end182# Finalize answers in response183# Check for empty response prior to sending184if req.answer.size < 1185req.header.rCode = Dnsruby::RCode::NOERROR186end187req.header.qr = true # Set response bit188send_response(cli, req.data)189end190191#192# Returns the hardcore alias for the DNS service193#194def self.hardcore_alias(*args)195"#{(args[0] || '')}-#{(args[1] || '')}-#{args[5] || ''}"196end197198#199# DNS server.200#201def alias202"DNS Server"203end204205206protected207#208# This method monitors the listener socket for new connections and calls209# the +on_client_connect+ callback routine.210#211def monitor_listener212while true213rds = [self.udp_sock]214wds = []215eds = [self.udp_sock]216217r,_,_ = ::IO.select(rds,wds,eds,1)218219if (r != nil and r[0] == self.udp_sock)220buf,host,port = self.udp_sock.recvfrom(65535)221# Mock up a client object for sending back data222cli = MockDnsClient.new(host, port, r[0])223dispatch_request(cli, buf)224end225end226end227228#229# Processes request coming from client230#231# @param cli [Rex::Socket::Tcp] Client sending request232def on_client_data(cli)233begin234data = cli.read(65535)235236raise ::EOFError if not data237raise ::EOFError if data.empty?238dispatch_request(cli, data)239rescue EOFError => e240self.tcp_socket.close_client(cli) if cli241raise e242end243end244245end246247end248end249end250251252