Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/proto/proxy/socks4a.rb
Views: 11704
# -*- coding: binary -*-1#2# sf - Sept 20103#4require 'thread'5require 'rex/socket'67module Rex8module Proto9module Proxy1011#12# A Socks4a proxy server.13#14class Socks4a1516#17# A client connected to the Socks4a server.18#19class Client2021REQUEST_VERSION = 422REPLY_VERSION = 02324COMMAND_CONNECT = 125COMMAND_BIND = 22627REQUEST_GRANTED = 9028REQUEST_REJECT_FAILED = 9129REQUEST_REJECT_CONNECT = 9230REQUEST_REJECT_USERID = 933132HOST = 133PORT = 23435#36# A Socks4a packet.37#38class Packet3940def initialize41@version = REQUEST_VERSION42@command = 043@dest_port = 044@dest_ip = '0.0.0.0'45@userid = ''46end4748#49# A helper function to recv in a Socks4a packet byte by byte.50#51# sf: we could just call raw = sock.get_once but some clients52# seem to need reading this byte by byte instead.53#54def Packet.recv( sock, timeout=30 )55raw = ''56# read in the 8 byte header57while( raw.length < 8 )58raw << sock.read( 1 )59end60# if its a request there will be more data61if( raw[0..0].unpack( 'C' ).first == REQUEST_VERSION )62# read in the userid63while( raw[8..raw.length].index( "\x00" ) == nil )64raw << sock.read( 1 )65end66# if a hostname is going to be present, read it in67ip = raw[4..7].unpack( 'N' ).first68if( ( ip & 0xFFFFFF00 ) == 0x00000000 and ( ip & 0x000000FF ) != 0x00 )69hostname = ''70while( hostname.index( "\x00" ) == nil )71hostname += sock.read( 1 )72end73raw << hostname74end75end76# create a packet from this raw data...77packet = Packet.new78packet.from_r( raw ) ? packet : nil79end8081#82# Pack a packet into raw bytes for transmitting on the wire.83#84def to_r85raw = [ @version, @command, @dest_port, Rex::Socket.addr_atoi( @dest_ip ) ].pack( 'CCnN' )86return raw if( @userid.empty? )87return raw + [ @userid ].pack( 'Z*' )88end8990#91# Unpack a raw packet into its components.92#93def from_r( raw )94return false if( raw.length < 8 )95@version = raw[0..0].unpack( 'C' ).first96return false if( @version != REQUEST_VERSION and @version != REPLY_VERSION )97@command = raw[1..1].unpack( 'C' ).first98@dest_port = raw[2..3].unpack( 'n' ).first99@dest_ip = Rex::Socket.addr_itoa( raw[4..7].unpack( 'N' ).first )100if( raw.length > 8 )101@userid = raw[8..raw.length].unpack( 'Z*' ).first102# if this is a socks4a request we can resolve the provided hostname103if( self.is_hostname? )104hostname = raw[(8+@userid.length+1)..raw.length].unpack( 'Z*' ).first105@dest_ip = self.resolve( hostname )106# fail if we couldnt resolve the hostname107return false if( not @dest_ip )108end109else110@userid = ''111end112return true113end114115def is_connect?116@command == COMMAND_CONNECT ? true : false117end118119def is_bind?120@command == COMMAND_BIND ? true : false121end122123attr_accessor :version, :command, :dest_port, :dest_ip, :userid124125protected126127#128# Resolve the given hostname into a dotted IP address.129#130def resolve( hostname )131if( not hostname.empty? )132begin133return Rex::Socket.getaddress(hostname, false)134rescue ::SocketError135return nil136end137end138return nil139end140141#142# As per the Socks4a spec, check to see if the provided dest_ip is 0.0.0.XX143# which indicates after the @userid field contains a hostname to resolve.144#145def is_hostname?146ip = Rex::Socket.addr_atoi( @dest_ip )147if( ip & 0xFFFFFF00 == 0x00000000 )148return true if( ip & 0x000000FF != 0x00 )149end150return false151end152153end154155#156# A mixin for a socket to perform a relay to another socket.157#158module Relay159160#161# Relay data coming in from relay_sock to this socket.162#163def relay( relay_client, relay_sock )164@relay_client = relay_client165@relay_sock = relay_sock166# start the relay thread (modified from Rex::IO::StreamAbstraction)167@relay_thread = Rex::ThreadFactory.spawn("SOCKS4AProxyServerRelay", false) do168loop do169closed = false170buf = nil171172begin173s = Rex::ThreadSafe.select( [ @relay_sock ], nil, nil, 0.2 )174if( s == nil || s[0] == nil )175next176end177rescue178closed = true179end180181if( closed == false )182begin183buf = @relay_sock.sysread( 32768 )184closed = true if( buf == nil )185rescue186closed = true187end188end189190if( closed == false )191total_sent = 0192total_length = buf.length193while( total_sent < total_length )194begin195data = buf[total_sent, buf.length]196sent = self.write( data )197if( sent > 0 )198total_sent += sent199end200rescue201closed = true202break203end204end205end206207if( closed )208@relay_client.stop209::Thread.exit210end211end212end213214end215216end217218#219# Create a new client connected to the server.220#221def initialize( server, sock )222@server = server223@lsock = sock224@rsock = nil225@client_thread = nil226@mutex = ::Mutex.new227end228229#230# Start handling the client connection.231#232def start233# create a thread to handle this client request so as to not block the socks4a server234@client_thread = Rex::ThreadFactory.spawn("SOCKS4AProxyClient", false) do235begin236@server.add_client( self )237# get the initial client request packet238request = Packet.recv( @lsock )239raise "Invalid Socks4 request packet received." if not request240# handle the request241begin242# handle socks4a connect requests243if( request.is_connect? )244# perform the connection request245params = {246'PeerHost' => request.dest_ip,247'PeerPort' => request.dest_port,248}249params['Context'] = @server.opts['Context'] if @server.opts.has_key?('Context')250251@rsock = Rex::Socket::Tcp.create( params )252# and send back success to the client253response = Packet.new254response.version = REPLY_VERSION255response.command = REQUEST_GRANTED256@lsock.put( response.to_r )257# handle socks4a bind requests258elsif( request.is_bind? )259# create a server socket for this request260params = {261'LocalHost' => '0.0.0.0',262'LocalPort' => 0,263}264params['Context'] = @server.opts['Context'] if @server.opts.has_key?('Context')265bsock = Rex::Socket::TcpServer.create( params )266# send back the bind success to the client267response = Packet.new268response.version = REPLY_VERSION269response.command = REQUEST_GRANTED270response.dest_ip = '0.0.0.0'271response.dest_port = bsock.getlocalname()[PORT]272@lsock.put( response.to_r )273# accept a client connection (2 minute timeout as per spec)274begin275::Timeout.timeout( 120 ) do276@rsock = bsock.accept277end278rescue ::Timeout::Error279raise "Timeout reached on accept request."280end281# close the listening socket282bsock.close283# verify the connection is from the dest_ip originally specified by the client284rpeer = @rsock.getpeername_as_array285raise "Got connection from an invalid peer." if( rpeer[HOST] != request.dest_ip )286# send back the client connect success to the client287#288# sf: according to the spec we send this response back to the client, however289# I have seen some clients who bawk if they get this second response.290#291response = Packet.new292response.version = REPLY_VERSION293response.command = REQUEST_GRANTED294response.dest_ip = rpeer[HOST]295response.dest_port = rpeer[PORT]296@lsock.put( response.to_r )297else298raise "Unknown request command received #{request.command} received."299end300rescue301# send back failure to the client302response = Packet.new303response.version = REPLY_VERSION304response.command = REQUEST_REJECT_FAILED305@lsock.put( response.to_r )306# raise an exception to close this client connection307raise "Failed to handle the clients request."308end309# setup the two way relay for full duplex io310@lsock.extend( Relay )311@rsock.extend( Relay )312# start the socket relays...313@lsock.relay( self, @rsock )314@rsock.relay( self, @lsock )315rescue316wlog( "Client.start - #{$!}" )317self.stop318end319end320end321322#323# Stop handling the client connection.324#325def stop326@mutex.synchronize do327if( not @closed )328329begin330@lsock.close if @lsock331rescue332end333334begin335@rsock.close if @rsock336rescue337end338339@client_thread.kill if( @client_thread and @client_thread.alive? )340341@server.remove_client( self )342343@closed = true344end345end346end347348end349350#351# Create a new Socks4a server.352#353def initialize( opts={} )354@opts = { 'ServerHost' => '0.0.0.0', 'ServerPort' => 1080 }355@opts = @opts.merge( opts )356@server = nil357@clients = ::Array.new358@running = false359@server_thread = nil360end361362#363# Check if the server is running.364#365def is_running?366return @running367end368369#370# Start the Socks4a server.371#372def start373begin374# create the servers main socket (ignore the context here because we don't want a remote bind)375@server = Rex::Socket::TcpServer.create(376'LocalHost' => @opts['ServerHost'],377'LocalPort' => @opts['ServerPort'],378'Comm' => @opts['Comm']379)380# signal we are now running381@running = true382# start the servers main thread to pick up new clients383@server_thread = Rex::ThreadFactory.spawn("SOCKS4AProxyServer", false) do384while( @running ) do385begin386# accept the client connection387sock = @server.accept388# and fire off a new client instance to handle it389Client.new( self, sock ).start390rescue391wlog( "Socks4a.start - server_thread - #{$!}" )392end393end394end395rescue396wlog( "Socks4a.start - #{$!}" )397return false398end399return true400end401402#403# Block while the server is running.404#405def join406@server_thread.join if @server_thread407end408409#410# Stop the Socks4a server.411#412def stop413if( @running )414# signal we are no longer running415@running = false416# stop any clients we have (create a new client array as client.stop will delete from @clients)417clients = []418clients.concat( @clients )419clients.each do | client |420client.stop421end422# close the server socket423@server.close if @server424# if the server thread did not terminate gracefully, kill it.425@server_thread.kill if( @server_thread and @server_thread.alive? )426end427return !@running428end429430def add_client( client )431@clients << client432end433434def remove_client( client )435@clients.delete( client )436end437438attr_reader :opts439440end441442end; end; end443444445446