# frozen_string_literal: true
#
# Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# ronin-support is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ronin-support is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with ronin-support. If not, see .
#
module Ronin
module Support
module Network
#
# Base class for {TCP::Proxy TCP} and {UDP::Proxy UDP} Proxies.
#
# ## Callbacks
#
# The Proxy base class supports several callbacks for proxy events.
#
# ### client_data
#
# When a client sends data to the proxy.
#
# on_client_data do |client,server,data|
# data.gsub!(/foo/,'bar')
# end
#
# ### server_data
#
# When the server sends data to the proxy.
#
# on_server_data do |client,server,data|
# data.gsub!(/foo/,'bar')
# end
#
# ### data
#
# Alias for {#on_client_data} and {#on_server_data}.
#
# ## Actions
#
# The Proxy base class also provides methods to change how events are
# handled.
#
# * {#ignore!}
# * {#close!}
# * {#reset!}
#
# @since 0.5.0
#
class Proxy
# Default host to bind to
DEFAULT_HOST = '0.0.0.0'
DEFAULT_BUFFER_SIZE = 4096
# The host the proxy will listen on
#
# @return [String]
attr_reader :host
# The port the proxy will listen on
#
# @return [Integer]
attr_reader :port
# The remote port the proxy will relay data to
#
# @return [String]
attr_reader :server_host
# The remote host the proxy will relay data to
#
# @return [Integer]
attr_reader :server_port
# The size of read buffer
#
# @return [Integer]
attr_accessor :buffer_size
# The connections maintained by the proxy
#
# @return [Hash{Socket => Socket}]
attr_reader :connections
#
# Creates a new Proxy.
#
# @param [String] host
# The host to listen on.
#
# @param [Integer] port
# The port to listen on.
#
# @param [String, (host, port)] server
# The server to forward connections to.
#
# @param [Integer] buffer_size
# The maximum amount of data to read in.
#
# @yield [proxy]
# If a block is given, it will be passed the new Proxy, before it
# has been configured.
#
# @yieldparam [Proxy] proxy
# The new Proxy object.
#
# @example Proxies `0.0.0.0:1337` to `victim.com:80`:
# Proxy.new(port: 1337, server: ['victim.com', 80])
#
# @example Proxies `localhost:25` to `victim.com:25`:
# Proxy.new(port: 25, host: 'localhost', server: 'victim.com')
#
def initialize(host: DEFAULT_HOST,
port: ,
server: ,
buffer_size: DEFAULT_BUFFER_SIZE)
@host = host
@port = port
@server_host, @server_port = server
@server_port ||= @port
@callbacks = {client_data: [], server_data: []}
@buffer_size = buffer_size
@connections = {}
yield self if block_given?
end
#
# Creates a new Proxy and begins relaying data.
#
# @param [Hash{Symbol => Object}] kwargs
# Additional keyword arguments for {#initialize}.
#
# @see #initialize
#
# @api public
#
def self.start(**kwargs,&block)
new(**kwargs,&block).start
end
#
# Starts the proxy and begins relaying data.
#
# @return [Proxy]
# The proxy object.
#
# @api public
#
def start
open
listen
close
return self
end
#
# Opens the proxy.
#
# @api public
#
# @abstract
#
def open
end
#
# Polls the connections for data or errors.
#
# @api public
#
# @abstract
#
def poll
end
#
# Polls the connections for data.
#
# @api public
#
def listen
@listening = true
while @listening
begin
poll
rescue Interrupt
@listening = false
break
end
end
end
#
# Sends data to a connection.
#
# @param [Socket] connection
# The connection.
#
# @param [String] data
# The data to send.
#
# @api public
#
# @abstract
#
def send(connection,data)
end
#
# Receives data from a connection.
#
# @param [Socket] connection
# The connection.
#
# @api public
#
# @abstract
#
def recv(connection)
end
#
# Closes the proxy.
#
# @api public
#
def close
close_connections
close_proxy
end
#
# Stops the proxy from listening.
#
# @api public
#
def stop
@listening = false
return self
end
#
# Registers a callback for when a client sends data.
#
# @yield [client, server, data]
# The given block will be passed the client connection, the server
# connection, and the received data.
#
# @yieldparam [Socket] client
# The connection to the client.
#
# @yieldparam [Socket] server
# The connection to the server.
#
# @yieldparam [String] data
# The recieved data.
#
# @api public
#
def on_client_data(&block)
@callbacks[:client_data] << block
end
#
# Registers a callback for when a server sends data.
#
# @yield [client, server, data]
# The given block will be passed the client connection, the server
# connection, and the received data.
#
# @yieldparam [Socket] client
# The connection to the client.
#
# @yieldparam [Socket] server
# The connection to the server.
#
# @yieldparam [String] data
# The recieved data.
#
# @api public
#
def on_server_data(&block)
@callbacks[:server_data] << block
end
#
# Registers a callback for when either the client or the server sends
# data.
#
# @yield [client, server, data]
# The given block will be passed the client connection, the server
# connection, and the received data.
#
# @yieldparam [Socket] client
# The connection to the client.
#
# @yieldparam [Socket] server
# The connection to the server.
#
# @yieldparam [String] data
# The recieved data.
#
# @api public
#
def on_data(&block)
on_client_data(&block)
on_server_data(&block)
end
#
# Causes the proxy to ignore a message.
#
# @api public
#
def ignore!
throw(:action,:ignore)
end
#
# Causes the proxy to close a connection.
#
# @api public
#
def close!
throw(:action,:close)
end
#
# Causes the proxy to restart a connection.
#
# @api public
#
def reset!
throw(:action,:reset)
end
#
# Causes the proxy to stop processing data entirely.
#
# @api public
#
def stop!
throw(:action,:stop)
end
#
# Connections from clients.
#
# @return [Array]
# Client connections.
#
def client_connections
@connections.keys
end
#
# Connections to the server.
#
# @return [Array]
# Server connections.
#
def server_connections
@connections.values
end
#
# Finds the connection to the server, associated with the client.
#
# @param [Socket] client_connection
# The connection from the client.
#
# @return [Socket]
# The connection to the server.
#
def server_connection_for(client_connection)
@connections[client_connection]
end
#
# Finds the connection from the client, associated with the server
# connection.
#
# @param [Socket] server_connection
# The connection to the server.
#
# @return [Socket]
# The connection from the client.
#
def client_connection_for(server_connection)
@connections.key(server_connection)
end
#
# Converts the proxy to a String.
#
# @return [String]
# The String form of the proxy.
#
def to_s
"#{@host}:#{@port} <-> #{@server_host}:#{@server_port}"
end
#
# Inspects the proxy.
#
# @return [String]
# The inspected proxy.
#
def inspect
"#<#{self.class}:#{self.object_id}: #{self}>"
end
protected
#
# Creates a new connection to the server.
#
# @return [Socket]
# The new connection.
#
# @abstract
#
def open_server_connection
end
#
# Closes a client connection to the proxy.
#
# @param [Socket] connection
# The client connection.
#
# @abstract
#
def close_client_connection(connection)
end
#
# Closes a connection to the server.
#
# @param [Socket] connection
# The server connection.
#
# @abstract
#
def close_server_connection(connection)
end
#
# Closes the proxy.
#
# @abstract
#
def close_proxy
end
#
# Resets a server connection.
#
# @param [Socket] client_connection
# The connection from the client to the proxy.
#
# @param [Socket] server_connection
# The connection from the proxy to the server.
#
def reset_connection(client_connection,server_connection)
close_server_connection(server_connection) if server_connection
@connections[client_connection] = open_server_connection
end
#
# Closes both the client and server connections.
#
# @param [Socket] client_connection
# The connection from the client to the proxy.
#
# @param [Socket] server_connection
# The connection from the proxy to the server.
#
def close_connection(client_connection,server_connection=nil)
close_server_connection(server_connection) if server_connection
close_client_connection(client_connection)
@connections.delete(client_connection)
end
#
# Closes all active connections.
#
def close_connections
@connections.each do |client_connection,server_connection|
close_server_connection(server_connection)
close_client_connection(client_connection)
end
@connections.clear
end
#
# Triggers the callbacks registered for an event.
#
# @param [Symbol] event
# The event being triggered.
#
# @param [Socket] client_connection
# The connection from the client to the proxy.
#
# @param [Socket] server_connection
# The connection from the proxy to the server.
#
# @param [String] data
# The data being sent.
#
# @yield []
# If none of the callbacks interrupted the event, the given block
# will be called.
#
def callback(event,client_connection,server_connection=nil,data=nil)
action = catch(:action) do
@callbacks[event].each do |block|
case block.arity
when 1
block.call(client_connection)
when 2
block.call(client_connection,server_connection)
when 3, -1
block.call(client_connection,server_connection,data)
end
end
end
case action
when :ignore
# no-op
when :reset
reset_connection(client_connection,server_connection)
when :close
close_connection(client_connection,server_connection)
when :stop
stop
else
yield if block_given?
end
end
#
# Triggers the `client_data` event.
#
# @param [Socket] client_connection
# The connection from a client to the proxy.
#
# @param [Socket] server_connection
# The connection from the proxy to the server.
#
# @param [String] data
# The data sent by the client.
#
def client_data(client_connection,server_connection,data)
callback(:client_data,client_connection,server_connection,data) do
send(server_connection,data)
end
end
#
# Triggers the `server_data` event.
#
# @param [Socket] client_connection
# The connection from a client to the proxy.
#
# @param [Socket] server_connection
# The connection from the proxy to the server.
#
# @param [String] data
# The data sent from the server.
#
def server_data(client_connection,server_connection,data)
callback(:server_data,client_connection,server_connection,data) do
send(client_connection,data)
end
end
end
end
end
end