J
Jørgen P. Tjernø
After having struggeled with libsock/ruby-sockets+socks, etc, and not
getting any way, I decided to try another approach. The whole
ruby-builting-socks / libsock idea is to make it as transparent / global
as possible, seemingly never meant to be controlled directly by the
application.
So, I implemented my own little TCPSocket-"wrapper"-class, which just
speaks the one command from SOCKSv4 and which only understands the one
reply. After that, it lets go of the socket. (SOCKS protocol information
found on http://en.wikipedia.org/wiki/SOCKS)
Then I just overrode Net::FTP#open_socket, so if ENV['SOCKS_SERVER'] is
set, it uses this my little class to set up a socks-connection. This can
of course be used for anything, not just Net::FTP - any other
Net::-module, or a custom creation of yours. Net::FTP just happens to be
what I wanted it for. ;-)
Now, I'm curious what you think of the code as a whole (coding style,
language use, approach, how to "hook into" Net::FTP, etc), and if you
have any suggestions. I'm thinking I should release this as public
domain, so anyone else in my situation won't have to bark up all the
wrong trees for as long as I did. ;-)
Thanks in advance!
Kindest regards, Jørgen P. Tjernø.
* code follows:
require 'socket'
require 'resolv'
require 'net/ftp'
class SocksProxying
REQUEST_GRANTED = 0x5A
REQUEST_FAILED = 0x5B
REQUEST_FAILED_NOIDENT = 0x5C
REQUEST_FAILED_IDENTINVALID = 0x5D
SOCKS_VERSION_4 = 0x04
SOCKS_CMD_CONNECTION = 0x01
SOCKS_CMD_BINDING = 0x02
SOCKS_REPLY_LENGTH = 8
def initialize
@socket = nil
end
def connect_via(proxy, proxy_port = 1080)
@socket = TCPSocket.open(proxy, proxy_port)
end
def connect(host, port, user = ENV['USER'])
if @socket.nil?
raise "Error: Must connect_via first, then connect."
end
ip = Resolv.getaddress(host).split(/\./).collect {|part| part.to_i}
port = port.to_i
data = [SOCKS_VERSION_4, SOCKS_CMD_CONNECTION, port, ip,
user].flatten.pack('CCnC4a' + (user.length + 1).to_s)
@socket.write data
dud, statuscode, port, ip =
@socket.recv(SOCKS_REPLY_LENGTH).unpack('CCnC4')
if statuscode != REQUEST_GRANTED
if statuscode == REQUEST_FAILED
raise "Error: SOCKS-access not granted by SOCKS-server."
elsif statuscode == REQUEST_FAILED_NOIDENT
raise "Error: SOCKS-connection failed; server could not
reach our ident server."
elsif statuscode == REQUEST_FAILED_IDENTINVALID
raise "Error: SOCKS-connection failed; server could not
match our ident reply to the one sent."
else
raise "Error: SOCKS-connection failed; unknown error-code."
end
end
@socket
end
end
# Here is where we override Net::FTP's internal socket creation.
class Net::FTP
def open_socket(host, port)
if ENV["SOCKS_SERVER"]
@passive = true
proxy_host, proxy_port = ENV["SOCKS_SERVER"].split /:/
sp = SocksProxying.new
sp.connect_via(proxy_host, proxy_port)
return sp.connect(host, port)
else
return TCPSocket.open(host, port)
end
end
end
getting any way, I decided to try another approach. The whole
ruby-builting-socks / libsock idea is to make it as transparent / global
as possible, seemingly never meant to be controlled directly by the
application.
So, I implemented my own little TCPSocket-"wrapper"-class, which just
speaks the one command from SOCKSv4 and which only understands the one
reply. After that, it lets go of the socket. (SOCKS protocol information
found on http://en.wikipedia.org/wiki/SOCKS)
Then I just overrode Net::FTP#open_socket, so if ENV['SOCKS_SERVER'] is
set, it uses this my little class to set up a socks-connection. This can
of course be used for anything, not just Net::FTP - any other
Net::-module, or a custom creation of yours. Net::FTP just happens to be
what I wanted it for. ;-)
Now, I'm curious what you think of the code as a whole (coding style,
language use, approach, how to "hook into" Net::FTP, etc), and if you
have any suggestions. I'm thinking I should release this as public
domain, so anyone else in my situation won't have to bark up all the
wrong trees for as long as I did. ;-)
Thanks in advance!
Kindest regards, Jørgen P. Tjernø.
* code follows:
require 'socket'
require 'resolv'
require 'net/ftp'
class SocksProxying
REQUEST_GRANTED = 0x5A
REQUEST_FAILED = 0x5B
REQUEST_FAILED_NOIDENT = 0x5C
REQUEST_FAILED_IDENTINVALID = 0x5D
SOCKS_VERSION_4 = 0x04
SOCKS_CMD_CONNECTION = 0x01
SOCKS_CMD_BINDING = 0x02
SOCKS_REPLY_LENGTH = 8
def initialize
@socket = nil
end
def connect_via(proxy, proxy_port = 1080)
@socket = TCPSocket.open(proxy, proxy_port)
end
def connect(host, port, user = ENV['USER'])
if @socket.nil?
raise "Error: Must connect_via first, then connect."
end
ip = Resolv.getaddress(host).split(/\./).collect {|part| part.to_i}
port = port.to_i
data = [SOCKS_VERSION_4, SOCKS_CMD_CONNECTION, port, ip,
user].flatten.pack('CCnC4a' + (user.length + 1).to_s)
@socket.write data
dud, statuscode, port, ip =
@socket.recv(SOCKS_REPLY_LENGTH).unpack('CCnC4')
if statuscode != REQUEST_GRANTED
if statuscode == REQUEST_FAILED
raise "Error: SOCKS-access not granted by SOCKS-server."
elsif statuscode == REQUEST_FAILED_NOIDENT
raise "Error: SOCKS-connection failed; server could not
reach our ident server."
elsif statuscode == REQUEST_FAILED_IDENTINVALID
raise "Error: SOCKS-connection failed; server could not
match our ident reply to the one sent."
else
raise "Error: SOCKS-connection failed; unknown error-code."
end
end
@socket
end
end
# Here is where we override Net::FTP's internal socket creation.
class Net::FTP
def open_socket(host, port)
if ENV["SOCKS_SERVER"]
@passive = true
proxy_host, proxy_port = ENV["SOCKS_SERVER"].split /:/
sp = SocksProxying.new
sp.connect_via(proxy_host, proxy_port)
return sp.connect(host, port)
else
return TCPSocket.open(host, port)
end
end
end