# -*- coding: binary -*-

module Msf

module EvasiveTCP
  attr_accessor :_send_size, :_send_delay, :evasive

  def denagle
    begin
      setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
    rescue ::Exception
    end
  end

  def write(buf, opts={})

    return super(buf, opts) if not @evasive

    ret = 0
    idx = 0
    len = @_send_size || buf.length

    while(idx < buf.length)

      if(@_send_delay and idx > 0)
        ::IO.select(nil, nil, nil, @_send_delay)
      end

      pkt = buf[idx, len]

      res = super(pkt, opts)
      flush()

      idx += len
      ret += res if res
    end
    ret
  end
end

###
#
# This module provides methods for establish a connection to a remote host and
# communicating with it.
#
###
module Exploit::Remote::Tcp

  #
  # Initializes an instance of an exploit module that exploits a
  # vulnerability in a TCP server.
  #
  def initialize(info = {})
    super

    register_options(
      [
        Opt::RHOST,
        Opt::RPORT
      ], Msf::Exploit::Remote::Tcp
    )

    register_advanced_options(
      [
        OptBool.new('SSL',        [ false, 'Negotiate SSL/TLS for outgoing connections', false]),
        OptString.new('SSLServerNameIndication', [ false, 'SSL/TLS Server Name Indication (SNI)', nil]),
        Opt::SSLVersion,
        OptEnum.new('SSLVerifyMode',  [ false, 'SSL verification method', 'PEER', %W{CLIENT_ONCE FAIL_IF_NO_PEER_CERT NONE PEER}]),
        OptString.new('SSLCipher',    [ false, 'String for SSL cipher - "DHE-RSA-AES256-SHA" or "ADH"']),
        OptString.new('SSLKeyLogFile', [ false, 'The SSL key log file', ENV['SSLKeyLogFile']]),
        Opt::Proxies,
        Opt::CPORT,
        Opt::CHOST,
        OptInt.new('ConnectTimeout', [ true, 'Maximum number of seconds to establish a TCP connection', 10])
      ], Msf::Exploit::Remote::Tcp
    )

    register_evasion_options(
      [
        OptInt.new('TCP::max_send_size', [false, 'Maxiumum tcp segment size.  (0 = disable)', 0]),
        OptInt.new('TCP::send_delay', [false, 'Delays inserted before every send.  (0 = disable)', 0])
      ], Msf::Exploit::Remote::Tcp
    )
  end

  #
  # Establishes a TCP connection to the specified RHOST/RPORT
  #
  # @see Rex::Socket::Tcp
  # @see Rex::Socket::Tcp.create
  def connect(global = true, opts={})

    dossl = false
    if(opts.has_key?('SSL'))
      dossl = opts['SSL']
    else
      dossl = ssl
      if (datastore.default?('SSL') and rport.to_i == 443)
        dossl = true
      end
    end

    nsock = Rex::Socket::Tcp.create(
      'PeerHost'      =>  opts['RHOST'] || rhost,
      'PeerHostname'  =>  opts['SSLServerNameIndication'] || opts['VHOST'] || opts['RHOSTNAME'],
      'PeerPort'      => (opts['RPORT'] || rport).to_i,
      'LocalHost'     =>  opts['CHOST'] || chost || "0.0.0.0",
      'LocalPort'     => (opts['CPORT'] || cport || 0).to_i,
      'SSL'           =>  dossl,
      'SSLVersion'    =>  opts['SSLVersion'] || ssl_version,
      'SSLVerifyMode' =>  opts['SSLVerifyMode'] || ssl_verify_mode,
      'SSLKeyLogFile' =>  opts['SSLKeyLogFile'] || sslkeylogfile,
      'SSLCipher'     =>  opts['SSLCipher'] || ssl_cipher,
      'Proxies'       => proxies,
      'Timeout'       => (opts['ConnectTimeout'] || connect_timeout || 10).to_i,
      'Comm'          =>  opts['Comm'],
      'Context'       =>
        {
          'Msf'        => framework,
          'MsfExploit' => self,
        })

    # enable evasions on this socket
    set_tcp_evasions(nsock)

    # Set this socket to the global socket as necessary
    self.sock = nsock if (global)

    # Add this socket to the list of sockets created by this exploit
    add_socket(nsock)

    return nsock
  end

  # Enable evasions on a given client
  def set_tcp_evasions(socket)

    if( datastore['TCP::max_send_size'].to_i == 0 and datastore['TCP::send_delay'].to_i == 0)
      return
    end

    return if socket.respond_to?('evasive')

    socket.extend(EvasiveTCP)

    if ( datastore['TCP::max_send_size'].to_i > 0)
      socket._send_size = datastore['TCP::max_send_size']
      socket.denagle
      socket.evasive = true
    end

    if ( datastore['TCP::send_delay'].to_i > 0)
      socket._send_delay = datastore['TCP::send_delay']
      socket.evasive = true
    end
  end

  def handler(nsock = self.sock)
    # If the handler claims the socket, then we don't want it to get closed
    # during cleanup
    if ((rv = super) == Handler::Claimed)
      if (nsock == self.sock)
        self.sock = nil
      end

      # Remove this socket from the list of sockets so that it will not be
      # aborted.
      remove_socket(nsock)
    end

    return rv
  end

  #
  # Shutdown the TCP connection
  #
  def shutdown(how = :SHUT_RDWR)
    self.sock.shutdown(how) if self.sock
  rescue IOError
  end

  #
  # Closes the TCP connection
  #
  def disconnect(nsock = self.sock)
    begin
      if (nsock)
        nsock.shutdown
        nsock.close
      end
    rescue IOError
    end

    if (nsock == sock)
      self.sock = nil
    end

    # Remove this socket from the list of sockets created by this exploit
    remove_socket(nsock)
  end

  #
  # Performs cleanup, disconnects the socket if necessary
  #
  def cleanup
    super
    disconnect
  end

  def print_prefix
    # Only inject a host/port prefix if we have exactly one entry.
    # Otherwise we are logging in the global context where rhost can be any
    # size (being an alias for rhosts), which is not very useful to insert into
    # a single log line.
    unless instance_variable_defined?(:@print_prefix)
      if rhost.present? && Rex::Socket::RangeWalker.new(rhost).length == 1
        @print_prefix = peer + ' - '
      else
        @print_prefix = ''
      end
    end

    super + @print_prefix
  end

  def replicant
    obj = super
    # invalidate the cached print_prefix in case the target changes
    obj.remove_instance_variable(:@print_prefix) if instance_variable_defined?(:@print_prefix)
    obj
  end

  ##
  #
  # Wrappers for getters
  #
  ##

  #
  # Returns the local host for outgoing connections
  #
  def chost
    datastore['CHOST']
  end

  #
  # Returns the TCP connection timeout
  #
  def connect_timeout
    datastore['ConnectTimeout']
  end

  #
  # Returns the local port for outgoing connections
  #
  def cport
    datastore['CPORT']
  end

  #
  # Returns the local host
  #
  def lhost
    datastore['LHOST']
  end

  #
  # Returns the local port
  #
  def lport
    datastore['LPORT']
  end

  # Returns the rhost:rport
  def peer
    Rex::Socket.to_authority(rhost, rport)
  end

  #
  # Returns the proxy configuration
  #
  def proxies
    datastore['Proxies']
  end

  #
  # Returns the target host
  #
  def rhost
    datastore['RHOST']
  end

  #
  # Returns the remote port
  #
  def rport
    datastore['RPORT']
  end

  #
  # Returns the boolean indicating SSL
  #
  def ssl
    datastore['SSL']
  end

  #
  # Returns the string indicating SSLVersion
  #
  def ssl_version
    datastore['SSLVersion']
  end

  #
  # Returns the SSL certification verification mechanism
  #
  def ssl_verify_mode
    datastore['SSLVerifyMode']
  end

  #
  # Returns the SSL key log file path
  #
  # @return [String]
  def sslkeylogfile
    datastore['SSLKeyLogFile']
  end

  #
  # Returns the SSL cipher to use for the context
  #
  def ssl_cipher
    datastore['SSLCipher']
  end

protected

  attr_accessor :sock

end

end
