class Rex::Services::LocalRelay

This service acts as a local TCP relay whereby clients can connect to a local listener that forwards to an arbitrary remote endpoint. Interaction with the remote endpoint socket requires that it implement the Rex::IO::Stream interface.

Attributes

relay_mutex[RW]
relay_thread[RW]
relays[RW]
rfds[RW]

Public Class Methods

hardcore_alias(*args) click to toggle source

Returns the hardcore alias for the local relay service.

# File lib/rex/services/local_relay.rb, line 128
def self.hardcore_alias(*args)
  "__#{args}"
end
new() click to toggle source

Initializes the local tcp relay monitor.

# File lib/rex/services/local_relay.rb, line 112
def initialize
  self.relays       = Hash.new
  self.rfds         = Array.new
  self.relay_thread = nil
  self.relay_mutex  = Mutex.new
end

Public Instance Methods

alias() click to toggle source

Returns the alias for this service.

Calls superclass method
# File lib/rex/services/local_relay.rb, line 135
def alias
  super || "Local Relay"
end
each_tcp_relay() { |opts || '0.0', opts, opts, opts, opts| ... } click to toggle source

Enumerate each TCP relay

# File lib/rex/services/local_relay.rb, line 250
def each_tcp_relay(&block)
  self.relays.each_pair { |name, relay|
    next if (relay.opts['__RelayType'] != 'tcp')

    yield(
      relay.opts['LocalHost'] || '0.0.0.0',
      relay.opts['LocalPort'],
      relay.opts['PeerHost'],
      relay.opts['PeerPort'],
      relay.opts)
  }
end
start() click to toggle source

Starts the thread that monitors the local relays.

# File lib/rex/services/local_relay.rb, line 142
def start
  if (!self.relay_thread)
    self.relay_thread = Rex::ThreadFactory.spawn("LocalRelay", false) {
      begin
        monitor_relays
      rescue ::Exception
        elog("Error in #{self} monitor_relays: #{$!}", 'rex')
      end
    }
  end
end
start_relay(stream_server, name, opts = {}) click to toggle source

Starts a local relay on the supplied local port. This listener will call the supplied callback procedures when various events occur.

# File lib/rex/services/local_relay.rb, line 206
def start_relay(stream_server, name, opts = {})
  # Create a Relay instance with the local stream and remote stream
  relay = Relay.new(name, stream_server, opts)

  # Extend the stream_server so that we can associate it with this relay
  stream_server.extend(StreamServer)
  stream_server.relay = relay

  # Add the stream associations the appropriate lists and hashes
  self.relay_mutex.synchronize {
    self.relays[name] = relay

    self.rfds << stream_server
  }
end
start_tcp_relay(lport, opts = {}) click to toggle source

Starts a local TCP relay.

# File lib/rex/services/local_relay.rb, line 186
def start_tcp_relay(lport, opts = {})
  # Make sure our options are valid
  if ((opts['PeerHost'] == nil or opts['PeerPort'] == nil) and (opts['Stream'] != true))
    raise ArgumentError, "Missing peer host or peer port.", caller
  end

  listener = Rex::Socket.create_tcp_server(
    'LocalHost' => opts['LocalHost'],
    'LocalPort' => lport)

  opts['LocalPort']   = lport
  opts['__RelayType'] = 'tcp'

  start_relay(listener, lport.to_s + (opts['LocalHost'] || '0.0.0.0'), opts)
end
stop() click to toggle source

Stops the thread that monitors the local relays and destroys all local listeners.

# File lib/rex/services/local_relay.rb, line 158
def stop
  if (self.relay_thread)
    self.relay_thread.kill
    self.relay_thread = nil
  end

  self.relay_mutex.synchronize {
    self.relays.delete_if { |k, v|
      v.shutdown
      v.close
      true
    }
  }

  # Flush the relay list and read fd list
  self.relays.clear
  self.rfds.clear
end
stop_relay(name) click to toggle source

Stops a relay with a given name.

# File lib/rex/services/local_relay.rb, line 232
def stop_relay(name)
  rv = false

  self.relay_mutex.synchronize {
    relay = self.relays[name]

    if (relay)
      close_relay(relay)
      rv = true
    end
  }

  rv
end
stop_tcp_relay(lport, lhost = nil) click to toggle source

Stops relaying on a given local port.

# File lib/rex/services/local_relay.rb, line 225
def stop_tcp_relay(lport, lhost = nil)
  stop_relay(lport.to_s + (lhost || '0.0.0.0'))
end

Protected Instance Methods

accept_relay_conn(srvfd) click to toggle source

Accepts a client connection on a local relay.

# File lib/rex/services/local_relay.rb, line 320
def accept_relay_conn(srvfd)
  relay = srvfd.relay

  begin
    dlog("Accepting relay client connection...", 'rex', LEV_3)

    # Accept the child connection
    lfd = srvfd.accept
    dlog("Got left side of relay: #{lfd}", 'rex', LEV_3)

    # Call the relay's on_local_connection method which should return a
    # remote connection on success
    rfd = srvfd.on_local_connection(relay, lfd)

    dlog("Got right side of relay: #{rfd}", 'rex', LEV_3)
  rescue
    wlog("Failed to get remote half of local connection on relay #{relay.name}: #{$!}", 'rex')
    lfd.close
    return
  end

  # If we have both sides, then we rock.  Extend the instances, associate
  # them with the relay, associate them with each other, and add them to
  # the list of polling file descriptors
  if (lfd and rfd)
    lfd.extend(Stream)
    rfd.extend(Stream)

    lfd.relay = relay
    rfd.relay = relay

    lfd.other_stream = rfd
    rfd.other_stream = lfd

    self.rfds << lfd
    self.rfds << rfd

  # Otherwise, we don't have both sides, we'll close them.
  else
    close_relay_conn(lfd)
  end
end
close_relay(relay) click to toggle source

Closes an cleans up a specific relay

# File lib/rex/services/local_relay.rb, line 271
def close_relay(relay)
  self.rfds.delete(relay.listener)
  self.relays.delete(relay.name)

  begin
    relay.shutdown
    relay.close
  rescue IOError
  end
end
close_relay_conn(fd) click to toggle source

Closes a specific relay connection without tearing down the actual relay itself.

# File lib/rex/services/local_relay.rb, line 286
def close_relay_conn(fd)
  relay = fd.relay
  ofd   = fd.other_stream

  self.rfds.delete(fd)

  begin
    if (relay.on_conn_close_proc)
      relay.on_conn_close_proc.call(fd)
    end

    fd.shutdown
    fd.close
  rescue IOError
  end

  if (ofd)
    self.rfds.delete(ofd)

    begin
      if (relay.on_conn_close_proc)
        relay.on_conn_close_proc.call(ofd)
      end

      ofd.shutdown
      ofd.close
    rescue IOError
    end
  end
end
monitor_relays() click to toggle source

Monitors the relays for data and passes it in both directions.

# File lib/rex/services/local_relay.rb, line 366
def monitor_relays
  begin
    # Helps with latency
    Thread.current.priority = 2

    # Poll all the streams...
    begin
      socks = Rex::ThreadSafe.select(rfds, nil, nil, 0.25)
    rescue StreamClosedError => e
      dlog("monitor_relays: closing stream #{e.stream}", 'rex', LEV_3)

      # Close the relay connection that is associated with the stream
      # closed error
      if (e.stream.kind_of?(Stream))
        close_relay_conn(e.stream)
      end

      dlog("monitor_relays: closed stream #{e.stream}", 'rex', LEV_3)

      next
    rescue
      elog("Error in #{self} monitor_relays select: #{$!.class} #{$!}", 'rex')
      return
    end

    # If socks is nil, go again.
    next unless socks

    # Process read-ready file descriptors, if any.
    socks[0].each { |rfd|

      # If this file descriptor is a server, accept the connection
      if (rfd.kind_of?(StreamServer))
        accept_relay_conn(rfd)
      # Otherwise, it's a relay connection, read data from one side
      # and write it to the other
      else
        begin
          # Pass the data onto the other fd, most likely writing it.
          data = rfd.sysread(65536)
          rfd.other_stream.on_other_data(data)
        # If we catch an error, close the connection
        rescue ::Exception
          elog("Error in #{self} monitor_relays read: #{$!}", 'rex')
          close_relay_conn(rfd)
        end
      end

    } if (socks[0])

  end while true
end