Renamed project to ruby-proxifier and renamed gem to proxifier.

This commit is contained in:
Samuel Kadolph 2011-07-19 16:11:35 -04:00
parent 653e57fa03
commit dad9540ef4
21 changed files with 387 additions and 343 deletions

View File

@ -1,46 +1,84 @@
# ruby-sockets
# ruby-proxifier
## Installing
### Recommended
```
gem install sockets
gem install proxifier
```
### Edge
```
git clone https://github.com/samuelkadolph/ruby-sockets
cd ruby-sockets && rake install
git clone https://github.com/samuelkadolph/ruby-proxifier
cd ruby-proxifier && rake install
```
## Rationale
This gem was created for 2 purposes.
First is to enable ruby programmers to use HTTP or SOCKS proxies interchangeably when using TCPSockets. Either manually with `Sockets::Proxy#open` or by `require "sockets/env"`.
First is to enable ruby programmers to use HTTP or SOCKS proxies
interchangeably when using TCPSockets. Either manually with
`Proxifier::Proxy#open` or by `require "proxifier/env"`.
The second purpose is to use ruby code that doesn't consider a proxy for users that have to use proxies.<br>
The pruby and pirb executables are simple wrappers for their respective ruby executables that support proxies from environment variables.
The second purpose is to use ruby code that doesn't user proxies for users that
have to use proxies.<br>The pruby and pirb executables are simple wrappers for
their respective ruby executables that support proxies from environment
variables.
## Usage
### Environment Variables & Executable Wrappers
### Executable Wrappers & Environment Variables
sockets provides two executables: `pruby` and `pirb`. They are simple wrappers
for your current `ruby` and `irb` executables that `require "sockets/env"`
which installs hooks to `TCPSocket` which will use your proxy environment
variables whenever a `TCPSocket` is created. sockets will use the
`proxy`, `PROXY`, `socks_proxy` and `http_proxy` environment variables (in that
order) to determine what proxy to use.
proxifier provides two executables: `pruby` and `pirb`. They are simple
wrappers for your current `ruby` and `irb` executables that requires the
`"proxifier/env"` script which installs hooks into `TCPSocket` which will use
the proxy environment variables to proxy any `TCPSocket`.
The environment variables that proxifier will check are (in order of descending
precedence):
<table>
<tr>
<th>Variable Name</th>
<th>Alternatives</th>
<th>Notes</th>
</tr>
<tr>
<td>proxy</td>
<td>PROXY</td>
<td>Requires the proxy scheme to be present.</td>
</tr>
<tr>
<td>socks_proxy</td>
<td>SOCKS_PROXY<br>socks5_proxy<br>SOCKS5_PROXY</td>
<td>Implies the SOCKS5 proxy scheme.</td>
</tr>
<tr>
<td>socks4a_proxy</td>
<td>SOCKS4A_PROXY</td>
<td>Implies the SOCKS4A proxy scheme.</td>
</tr>
<tr>
<td>socks4_proxy</td>
<td>PROXY</td>
<td>Implies the SOCKS4 proxy scheme.</td>
</tr>
<tr>
<td>http_proxy</td>
<td>HTTP_PROXY</td>
<td>Implies the HTTP proxy scheme.</td>
</tr>
</table>
### Ruby
```ruby
require "sockets/proxy"
require "proxifier/proxy"
proxy = Sockets::Proxy("socks://localhost")
proxy = Proxifier::Proxy("socks://localhost")
socket = proxy.open("www.google.com", 80)
socket << "GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n"
socket.gets # => "HTTP/1.1 200 OK\r\n"
@ -69,14 +107,14 @@ socks5://[username[:password]@]host[:port]</pre></td>
Port defaults to 1080.
</td>
</tr>
<tr>
<td>SOCKS4</td>
<td><pre>socks4://[username@]ip1.ip2.ip3.ip4[:port]</pre></td>
<td>Currently hangs. Not sure if the problem is with code or server.</td>
</tr>
<tr>
<td>SOCKS4A</td>
<td><pre>socks4a://[username@]host[:port]</pre></td>
<td>Not yet implemented.</td>
</tr>
<tr>
<td>SOCKS4</td>
<td><pre>socks4://[username@]host[:port]</pre></td>
<td>Currently hangs. Not sure if the problem is with code or server.</td>
</tr>
</table>

View File

@ -1,8 +1,8 @@
#!/usr/bin/env ruby
executable = File.expand_path("../" + Gem.default_exec_format % "irb", Gem.ruby)
full_gem_path = Gem.loaded_specs["sockets"].full_gem_path
load_paths = Gem.loaded_specs["sockets"].require_paths.map { |p| "-I#{File.join(full_gem_path, p)}" }
full_gem_path = Gem.loaded_specs["proxifier"].full_gem_path
load_paths = Gem.loaded_specs["proxifier"].require_paths.map { |p| "-I#{File.join(full_gem_path, p)}" }
# TODO: support argument switches
exec(executable, *load_paths, "-rsockets/env", *ARGV)
exec(executable, *load_paths, "-rproxifier/env", *ARGV)

View File

@ -1,8 +1,8 @@
#!/usr/bin/env ruby
executable = Gem.ruby
full_gem_path = Gem.loaded_specs["sockets"].full_gem_path
load_paths = Gem.loaded_specs["sockets"].require_paths.map { |p| "-I#{File.join(full_gem_path, p)}" }
full_gem_path = Gem.loaded_specs["proxifier"].full_gem_path
load_paths = Gem.loaded_specs["proxifier"].require_paths.map { |p| "-I#{File.join(full_gem_path, p)}" }
# TODO: support argument switches
exec(executable, *load_paths, "-rsockets/env", *ARGV)
exec(executable, *load_paths, "-rproxifier/env", *ARGV)

25
lib/proxifier.rb Normal file
View File

@ -0,0 +1,25 @@
require "uri"
require "uri/socks"
module Proxifier
require "proxifier/version"
autoload :HTTPProxy, "proxifier/proxies/http"
autoload :SOCKSProxy, "proxifier/proxies/socks"
autoload :SOCKS5Proxy, "proxifier/proxies/socks"
autoload :SOCKS4Proxy, "proxifier/proxies/socks4"
autoload :SOCKS4AProxy, "proxifier/proxies/socks4a"
def self.Proxy(url, options = {})
url = URI.parse(url)
raise(ArgumentError, "proxy url has no scheme") unless url.scheme
begin
klass = const_get("#{url.scheme.upcase}Proxy")
rescue NameError
raise(ArgumentError, "unknown proxy scheme `#{url.scheme}'")
end
klass.new(url, options)
end
end

View File

@ -1,6 +1,24 @@
require "sockets/proxy"
require "socket"
require "proxifier"
module Proxifier
class Proxy
def open(host, port, local_host = nil, local_port = nil)
return TCPSocket.new(host, port, local_host, local_port, :proxy => nil) unless proxify?(host)
socket = TCPSocket.new(proxy.host, proxy.port, local_host, local_port, :proxy => nil)
begin
proxify(socket, host, port)
rescue
socket.close
raise
end
socket
end
end
module Sockets
module Proxify
def self.included(klass)
klass.class_eval do
@ -18,11 +36,11 @@ module Sockets
options = options_if_local_host
end
if options[:proxy] && (proxy = Sockets::Proxy(options.delete(:proxy), options)) && proxy.proxify?(host)
if options[:proxy] && (proxy = Proxifier::Proxy(options.delete(:proxy), options)) && proxy.proxify?(host)
initialize_without_proxy(proxy.host, proxy.port, local_host, local_port)
begin
proxy.proxify(self, host, port)
rescue Exception
rescue
close
raise
end
@ -31,9 +49,7 @@ module Sockets
end
end
end
end
module Sockets
module EnvironmentProxify
def self.included(klass)
klass.class_eval do
@ -66,12 +82,30 @@ module Sockets
module ClassMethods
def environment_proxy
ENV["proxy"] || ENV["PROXY"] || ENV["socks_proxy"] || ENV["http_proxy"]
ENV["proxy"] || ENV["PROXY"] || specific_environment_proxy
end
def environment_no_proxy
ENV["no_proxy"]
ENV["no_proxy"] || ENV["NO_PROXY"]
end
private
def specific_environment_proxy
%w(socks socks5 socks4a socks4 http).each do |type|
if proxy = ENV["#{type}_proxy"] || ENV["#{type.upcase}_PROXY"]
scheme = "#{type}://"
proxy = proxy.dup
proxy.insert(0, scheme) unless proxy.index(scheme) == 0
return proxy
end
end
end
end
end
end
class TCPSocket
include Proxifier::Proxify
include Proxifier::EnvironmentProxify
end

View File

@ -0,0 +1,19 @@
require "net/http"
require "proxifier/proxy"
module Proxifier
class HTTPProxy < Proxy
def do_proxify(socket, host, port)
return if query_options["tunnel"] == "false"
socket << "CONNECT #{host}:#{port} HTTP/1.1\r\n"
socket << "Host: #{host}:#{port}\r\n"
socket << "Proxy-Authorization: Basic #{["#{user}:#{password}"].pack("m").chomp}\r\n" if user
socket << "\r\n"
buffer = Net::BufferedIO.new(socket)
response = Net::HTTPResponse.read_new(buffer)
response.error! unless response.is_a?(Net::HTTPOK)
end
end
end

View File

@ -0,0 +1,103 @@
require "ipaddr"
require "proxifier/proxy"
module Proxifier
class SOCKSProxy < Proxy
VERSION = 0x05
def do_proxify(socket, host, port)
authenticaton_method = greet(socket)
authenticate(socket, authenticaton_method)
connect(socket, host, port)
end
protected
def greet(socket)
methods = authentication_methods
socket << [VERSION, methods.size, *methods].pack("CCC#{methods.size}")
version, authentication_method = socket.read(2).unpack("CC")
check_version(version)
authentication_method
end
def authenticate(socket, method)
case method
when 0x00 # NO AUTHENTICATION REQUIRED
when 0x02 # USERNAME/PASSWORD
user &&= user[0, 0xFF]
password &&= password[0, 0xFF]
socket << [user.size, user, password.size, password].pack("CA#{user.size}CA#{password.size}")
version, status = socket.read(2).unpack("CC")
check_version(version)
case status
when 0x00 # SUCCESS
else
raise "SOCKS5 username/password authentication failed"
end
else
raise "no acceptable SOCKS5 authentication methods"
end
end
def connect(socket, host, port)
host = host[0, 0xFF]
socket << [VERSION, 0x01, 0x00, 0x03, host.size, host, port].pack("CCCCCA#{host.size}n")
version, status, _, type = socket.read(4).unpack("CCCC")
check_version(version)
case status
when 0x00 # succeeded
when 0x01 # general SOCKS server failure
raise "general SOCKS server failure"
when 0x02 # connection not allowed by ruleset
raise "connection not allowed by ruleset"
when 0x03 # Network unreachable
raise "network unreachable"
when 0x04 # Host unreachable
raise "host unreachable"
when 0x05 # Connection refused
raise "connection refused"
when 0x06 # TTL expired
raise "TTL expired"
when 0x07 # Command not supported
raise "command not supported"
when 0x08 # Address type not supported
raise "address type not supported"
else # unassigned
raise "unknown SOCKS error"
end
case type
when 0x01 # IP V4 address
destination = IPAddr.ntop(socket.read(4))
when 0x03 # DOMAINNAME
length = socket.read(1).unpack("C").first
destination = socket.read(length).unpack("A#{length}")
when 0x04 # IP V6 address
destination = IPAddr.ntop(socket.read(16))
else
raise "unsupported SOCKS5 address type"
end
port = socket.read(2).unpack("n").first
end
def check_version(version, should_be = VERSION)
raise "mismatched SOCKS version" unless version == should_be
end
private
def authentication_methods
methods = []
methods << 0x00 # NO AUTHENTICATION REQUIRED
methods << 0x02 if user # USERNAME/PASSWORD
methods
end
end
SOCKS5Proxy = SOCKSProxy
end

View File

@ -0,0 +1,44 @@
require "proxifier/proxies/socks"
module Proxifier
class SOCKS4Proxy < SOCKSProxy
VERSION = 0x04
protected
def greet(socket)
# noop
end
def authenticate(socket, method)
# noop
end
def connect(socket, host, port)
begin
ip = IPAddr.new(host)
rescue ArgumentError
ip = IPAddr.new(Socket.getaddrinfo(host, nil, :INET, :STREAM).first)
end
socket << [VERSION, 0x01, port].pack("CCn") << ip.hton
socket << user if user
socket << 0x00
version, status, port = socket.read(4).unpack("CCn")
check_version(version, 0x00)
ip = IPAddr.ntop(socket.read(4))
case status
when 0x5A # request granted
when 0x5B # request rejected or failed
raise "request rejected or failed"
when 0x5C # request rejected becasue SOCKS server cannot connect to identd on the client
raise "request rejected becasue SOCKS server cannot connect to identd on the client"
when 0x5D # request rejected because the client program and identd report different user-ids
raise "request rejected because the client program and identd report different user-ids"
else
raise "unknown SOCKS error"
end
end
end
end

View File

@ -0,0 +1,9 @@
require "proxifier/proxies/socks"
module Proxifier
class SOCKS4AProxy < SOCKSProxy
def do_proxify(*)
raise NotImplementedError, "SOCKS4A is not yet implemented"
end
end
end

63
lib/proxifier/proxy.rb Normal file
View File

@ -0,0 +1,63 @@
require "socket"
require "uri"
require "uri/socks"
module Proxifier
class Proxy
class << self
def proxify?(host, no_proxy = nil)
return true unless no_proxy
dont_proxy = no_proxy.split(",")
dont_proxy.none? { |h| host =~ /#{h}\Z/ }
end
end
attr_reader :url, :options
def initialize(url, options = {})
url = URI.parse(uri) unless url.is_a?(URI::Generic)
@url, @options = url, options
end
def open(host, port, local_host = nil, local_port = nil)
return TCPSocket.new(host, port, local_host, local_port) unless proxify?(host)
socket = TCPSocket.new(proxy.host, proxy.port, local_host, local_port)
begin
proxify(socket, host, port)
rescue
socket.close
raise
end
socket
end
def proxify?(host)
self.class.proxify?(host, options[:no_proxy])
end
def proxify(socket, host, port)
do_proxify(socket, host, port)
end
%w(host port user password query version).each do |attr|
class_eval "def #{attr}; url.#{attr} end"
end
def query_options
@query_options ||= query ? Hash[query.split("&").map { |q| q.split("=") }] : {}
end
%w(no_proxy).each do |option|
class_eval "def #{option}; options[:#{option}] end"
end
protected
def do_proxify(socket, host, port)
raise NotImplementedError, "#{self} must implement do_proxify"
end
end
end

View File

@ -1,3 +1,3 @@
module Sockets
module Proxifier
VERSION = "1.0.0"
end

View File

@ -1,5 +0,0 @@
require "sockets/version"
require "sockets/proxy"
module Sockets
end

View File

@ -1,28 +0,0 @@
require "socket"
require "sockets"
require "sockets/proxify"
module Sockets
class Proxy
def open(host, port, local_host = nil, local_port = nil)
if proxify?(host)
socket = TCPSocket.new(proxy.host, proxy.port, local_host, local_port, :proxy => nil)
begin
proxify(socket, host, port)
rescue Exception
socket.close
raise
end
socket
else
TCPSocket.new(host, port, local_host, local_port, :proxy => nil)
end
end
end
end
class TCPSocket
include Sockets::Proxify
include Sockets::EnvironmentProxify
end

View File

@ -1,4 +0,0 @@
module Sockets
module Proxies
end
end

View File

@ -1,21 +0,0 @@
require "net/http"
require "sockets/proxy"
module Sockets
module Proxies
class HTTP < Proxy
def do_proxify(socket, host, port)
return if query_options["tunnel"] == "false"
socket << "CONNECT #{host}:#{port} HTTP/1.1\r\n"
socket << "Host: #{host}:#{port}\r\n"
socket << "Proxy-Authorization: Basic #{["#{user}:#{password}"].pack("m").chomp}\r\n" if user
socket << "\r\n"
buffer = Net::BufferedIO.new(socket)
response = Net::HTTPResponse.read_new(buffer)
response.error! unless response.is_a?(Net::HTTPOK)
end
end
end
end

View File

@ -1,103 +0,0 @@
require "ipaddr"
require "sockets/proxy"
module Sockets
module Proxies
class SOCKS < Proxy
VERSION = 0x05
def do_proxify(socket, host, port)
authenticaton_method = greet(socket)
authenticate(socket, authenticaton_method)
connect(socket, host, port)
end
protected
def greet(socket)
methods = authentication_methods
socket << [VERSION, methods.size, *methods].pack("CCC#{methods.size}")
version, authentication_method = socket.read(2).unpack("CC")
check_version(version)
authentication_method
end
def authenticate(socket, method)
case method
when 0x00 # NO AUTHENTICATION REQUIRED
when 0x02 # USERNAME/PASSWORD
user &&= user[0, 0xFF]
password &&= password[0, 0xFF]
socket << [user.size, user, password.size, password].pack("CA#{user.size}CA#{password.size}")
version, status = socket.read(2).unpack("CC")
check_version(version)
case status
when 0x00 # SUCCESS
else
raise "SOCKS5 username/password authentication failed"
end
else
raise "no acceptable SOCKS5 authentication methods"
end
end
def connect(socket, host, port)
host = host[0, 0xFF]
socket << [VERSION, 0x01, 0x00, 0x03, host.size, host, port].pack("CCCCCA#{host.size}n")
version, status, _, type = socket.read(4).unpack("CCCC")
check_version(version)
case status
when 0x00 # succeeded
when 0x01 # general SOCKS server failure
raise "general SOCKS server failure"
when 0x02 # connection not allowed by ruleset
raise "connection not allowed by ruleset"
when 0x03 # Network unreachable
raise "network unreachable"
when 0x04 # Host unreachable
raise "host unreachable"
when 0x05 # Connection refused
raise "connection refused"
when 0x06 # TTL expired
raise "TTL expired"
when 0x07 # Command not supported
raise "command not supported"
when 0x08 # Address type not supported
raise "address type not supported"
else # unassigned
raise "unknown SOCKS error"
end
case type
when 0x01 # IP V4 address
destination = IPAddr.ntop(socket.read(4))
when 0x03 # DOMAINNAME
length = socket.read(1).unpack("C").first
destination = socket.read(length).unpack("A#{length}")
when 0x04 # IP V6 address
destination = IPAddr.ntop(socket.read(16))
else
raise "unsupported SOCKS5 address type"
end
port = socket.read(2).unpack("n").first
end
def check_version(version, should_be = VERSION)
raise "mismatched SOCKS version" unless version == should_be
end
private
def authentication_methods
methods = []
methods << 0x00 # NO AUTHENTICATION REQUIRED
methods << 0x02 if user # USERNAME/PASSWORD
methods
end
end
end
end

View File

@ -1,46 +0,0 @@
require "sockets/proxies/socks"
module Sockets
module Proxies
class SOCKS4 < SOCKS
VERSION = 0x04
protected
def greet(socket)
# noop
end
def authenticate(socket, method)
# noop
end
def connect(socket, host, port)
begin
ip = IPAddr.new(host)
rescue ArgumentError
ip = IPAddr.new(Socket.getaddrinfo(host, nil, :INET, :STREAM).first)
end
socket << [VERSION, 0x01, port].pack("CCn") << ip.hton
socket << user if user
socket << 0x00
version, status, port = socket.read(4).unpack("CCn")
check_version(version, 0x00)
ip = IPAddr.ntop(socket.read(4))
case status
when 0x5A # request granted
when 0x5B # request rejected or failed
raise "request rejected or failed"
when 0x5C # request rejected becasue SOCKS server cannot connect to identd on the client
raise "request rejected becasue SOCKS server cannot connect to identd on the client"
when 0x5D # request rejected because the client program and identd report different user-ids
raise "request rejected because the client program and identd report different user-ids"
else
raise "unknown SOCKS error"
end
end
end
end
end

View File

@ -1,9 +0,0 @@
module Sockets
module Proxies
class SOCKS4A < Proxy
def do_proxify(*)
raise NotImplementedError, "SOCKS4A is not yet implemented"
end
end
end
end

View File

@ -1,75 +0,0 @@
require "socket"
require "uri"
require "uri/socks"
module Sockets
class Proxy
attr_reader :url, :options
def initialize(url, options = {})
url = URI.parse(uri) unless url.is_a?(URI::Generic)
@url, @options = url, options
end
def open(host, port, local_host = nil, local_port = nil)
if proxify?(host)
socket = TCPSocket.new(proxy.host, proxy.port, local_host, local_port)
begin
proxify(socket, host, port)
rescue Exception
socket.close
raise
end
socket
else
TCPSocket.new(host, port, local_host, local_port)
end
end
def proxify?(host)
return true unless options[:no_proxy]
dont_proxy = options[:no_proxy].split(",")
dont_proxy.none? { |h| host =~ /#{h}\Z/ }
end
def proxify(socket, host, port)
do_proxify(socket, host, port)
end
%w(host port user password query version).each do |attr|
class_eval "def #{attr}; url.#{attr} end", __FILE__, __LINE__
end
def query_options
@query ||= query ? Hash[query.split("&").map { |q| q.split("=") }] : {}
end
%w(no_proxy).each do |option|
class_eval "def #{option}; options[:#{option}] end", __FILE__, __LINE__
end
protected
def do_proxify(socket, host, port)
raise NotImplementedError, "#{self} must implement do_proxify"
end
end
def self.Proxy(url, options = {})
url = URI.parse(url)
raise(ArgumentError, "proxy has no scheme") unless url.scheme
begin
klass = Proxies.const_get(url.scheme.upcase)
rescue NameError
begin
require "sockets/proxies/#{url.scheme}"
klass = Proxies.const_get(url.scheme.upcase)
rescue LoadError, NameError
raise(ArgumentError, "unknown proxy scheme `#{url.scheme}'")
end
end
klass.new(url, options)
end
end

16
proxifier.gemspec Normal file
View File

@ -0,0 +1,16 @@
$:.push File.expand_path("../lib", __FILE__)
require "proxifier/version"
Gem::Specification.new do |s|
s.name = "proxifier"
s.version = Proxifier::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ["Samuel Kadolph"]
s.email = ["samuel@kadolph.com"]
s.homepage = "https://github.com/samuelkadolph/ruby-proxifier"
s.summary = %q{}
s.description = %q{}
s.files = Dir["bin/*", "lib/**/*"] + ["LICENSE", "README.md"]
s.executables = ["pirb", "pruby"]
end

View File

@ -1,16 +0,0 @@
$:.push File.expand_path("../lib", __FILE__)
require "sockets/version"
Gem::Specification.new do |s|
s.name = "sockets"
s.version = Sockets::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ["Samuel Kadolph"]
s.email = ["samuel@kadolph.com"]
s.homepage = "https://github.com/samuelkadolph/ruby-sockets"
s.summary = %q{}
s.description = %q{}
s.files = Dir["{bin,lib}/**/*"] + ["LICENSE", "README.md"]
s.executables = ["pruby", "pirb"]
end