# -*- coding: binary -*-

require 'socket'
require 'fcntl'

module Rex
module IO

###
#
# This class provides an abstraction to a stream based
# connection through the use of a streaming socketpair.
#
###
module StreamAbstraction

  ###
  #
  # Extension information for required Stream interface.
  #
  ###
  module Ext

    #
    # Initializes peer information.
    #
    def initinfo(peer,local)
      @peer = peer
      @local = local
    end

    #
    # Symbolic peer information.
    #
    def peerinfo
      (@peer || "Remote Pipe")
    end

    #
    # Symbolic local information.
    #
    def localinfo
      (@local || "Local Pipe")
    end
  end

  #
  # This method creates a streaming socket pair and initializes it.
  #
  def initialize_abstraction
    self.lsock, self.rsock = Rex::Socket.tcp_socket_pair()
    self.lsock.extend(Rex::IO::Stream)
    self.lsock.extend(Ext)
    self.rsock.extend(Rex::IO::Stream)

    self.monitor_rsock
  end

  #
  # This method cleans up the abstraction layer.
  #
  def cleanup_abstraction
    self.lsock.close if (self.lsock)
    self.rsock.close if (self.rsock)

    self.lsock = nil
    self.rsock = nil
  end

  #
  # Low-level write to the local side.
  #
  def syswrite(buffer)
    lsock.syswrite(buffer)
  end

  #
  # Low-level read from the local side.
  #
  def sysread(length)
    lsock.sysread(length)
  end

  #
  # Shuts down the local side of the stream abstraction.
  #
  def shutdown(how)
    lsock.shutdown(how)
  end

  #
  # Closes both sides of the stream abstraction.
  #
  def close
    cleanup_abstraction
  end

  #
  # Symbolic peer information.
  #
  def peerinfo
    "Remote-side of Pipe"
  end

  #
  # Symbolic local information.
  #
  def localinfo
    "Local-side of Pipe"
  end

  #
  # The left side of the stream.
  #
  attr_reader :lsock
  #
  # The right side of the stream.
  #
  attr_reader :rsock

protected

  def monitor_rsock
    self.monitor_thread = Rex::ThreadFactory.spawn("StreamMonitorRemote", false) {
      loop do
        closed = false
        buf    = nil

        if not self.rsock
          wlog("monitor_rsock: the remote socket is nil, exiting loop")
          break
        end

        begin
          s = Rex::ThreadSafe.select( [ self.rsock ], nil, nil, 0.2 )
          if( s == nil || s[0] == nil )
            next
          end
        rescue Exception => e
          wlog("monitor_rsock: exception during select: #{e.class} #{e}")
          closed = true
        end

        if( closed == false )
          begin
            buf = self.rsock.sysread( 32768 )
            if buf == nil
              closed = true
              wlog("monitor_rsock: closed remote socket due to nil read")
            end
          rescue EOFError => e
            closed = true
            dlog("monitor_rsock: EOF in rsock")
          rescue ::Exception => e
            closed = true
            wlog("monitor_rsock: exception during read: #{e.class} #{e}")
          end
        end

        if( closed == false )
          total_sent   = 0
          total_length = buf.length
          while( total_sent < total_length )
            begin
              data = buf[total_sent, buf.length]

              # Note that this must be write() NOT syswrite() or put() or anything like it.
              # Using syswrite() breaks SSL streams.
              sent = self.write( data )

              # sf: Only remove the data off the queue is write was successfull.
              #     This way we naturally perform a resend if a failure occured.
              #     Catches an edge case with meterpreter TCP channels where remote send
              #     failes gracefully and a resend is required.
              if (sent.nil?)
                closed = true
                wlog("monitor_rsock: failed writing, socket must be dead")
                break
              elsif (sent > 0)
                total_sent += sent
              end
            rescue ::IOError, ::EOFError => e
              closed = true
              wlog("monitor_rsock: exception during write: #{e.class} #{e}")
              break
            end
          end
        end

        if( closed )
          begin
            self.close_write if self.respond_to?('close_write')
          rescue IOError
          end
          break
        end
      end
    }
  end

protected
  attr_accessor :monitor_thread
  attr_writer :lsock
  attr_writer :rsock

end

end; end

