class RubySMB::Dispatcher::Socket

This class provides a wrapper around a Socket for the packet Dispatcher. It allows for dependency injection of different Socket implementations.

Constants

READ_TIMEOUT

Attributes

read_timeout[RW]

The read timeout @!attribute [rw] read_timeout

@return [Integer]
tcp_socket[RW]

The underlying socket that we select on @!attribute [rw] tcp_socket

@return [IO]

Public Class Methods

connect(host, port: 445, socket: TCPSocket.new(host, port)) click to toggle source

@param host [String] passed to TCPSocket.new @param port [Fixnum] passed to TCPSocket.new

# File lib/ruby_smb/dispatcher/socket.rb, line 28
def self.connect(host, port: 445, socket: TCPSocket.new(host, port))
  new(socket)
end
new(tcp_socket, read_timeout: READ_TIMEOUT) click to toggle source

@param tcp_socket [IO]

# File lib/ruby_smb/dispatcher/socket.rb, line 20
def initialize(tcp_socket, read_timeout: READ_TIMEOUT)
  @tcp_socket = tcp_socket
  @tcp_socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if @tcp_socket.respond_to?(:setsockopt)
  @read_timeout = read_timeout
end

Public Instance Methods

recv_packet(full_response: false) click to toggle source

Read a packet off the wire and parse it into a string

@param full_response [Boolean] whether to include the NetBios Session Service header in the response @return [String] the raw response (including the NetBios Session Service header if full_response is true) @raise [RubySMB::Error::NetBiosSessionService] if there's an error reading the first 4 bytes,

which are assumed to be the NetBiosSessionService header.

@raise [RubySMB::Error::CommunicationError] if the read timeout expires or an error occurs when reading the socket

# File lib/ruby_smb/dispatcher/socket.rb, line 63
def recv_packet(full_response: false)
  raise RubySMB::Error::CommunicationError, 'Connection has already been closed' if @tcp_socket.closed?
  if IO.select([@tcp_socket], nil, nil, @read_timeout).nil?
    raise RubySMB::Error::CommunicationError, "Read timeout expired when reading from the Socket (timeout=#{@read_timeout})"
  end

  begin
    nbss_data = @tcp_socket.read(4)
    raise RubySMB::Error::CommunicationError, 'Socket read returned nil' if nbss_data.nil?
    nbss_header = RubySMB::Nbss::SessionHeader.read(nbss_data)
  rescue IOError
    raise ::RubySMB::Error::NetBiosSessionService, 'NBSS Header is missing'
  end

  length = nbss_header.stream_protocol_length
  data = full_response ? nbss_header.to_binary_s : ''
  if length > 0
    if IO.select([@tcp_socket], nil, nil, @read_timeout).nil?
      raise RubySMB::Error::CommunicationError, "Read timeout expired when reading from the Socket (timeout=#{@read_timeout})"
    end
    data << @tcp_socket.read(length)
    data << @tcp_socket.read(length - data.length) while data.length < length
  end
  data
rescue Errno::EINVAL, Errno::ECONNABORTED, Errno::ECONNRESET, TypeError, NoMethodError => e
  raise RubySMB::Error::CommunicationError, "An error occurred reading from the Socket #{e.message}"
end
send_packet(packet, nbss_header: true) click to toggle source

@param packet [SMB2::Packet,#to_s] @param nbss [Boolean] wether to include the NetBIOS Session header @return [void]

# File lib/ruby_smb/dispatcher/socket.rb, line 35
def send_packet(packet, nbss_header: true)
  data = nbss_header ? nbss(packet) : ''
  data << packet.to_binary_s
  bytes_written = 0
  begin
    while bytes_written < data.size
      retval = @tcp_socket.write(data[bytes_written..-1])

      if retval == nil
        raise RubySMB::Error::CommunicationError
      else
        bytes_written += retval
      end
    end

  rescue IOError, Errno::ECONNABORTED, Errno::ECONNRESET => e
    raise RubySMB::Error::CommunicationError, "An error occurred writing to the Socket: #{e.message}"
  end
  nil
end