pax_global_header00006660000000000000000000000064121533145340014513gustar00rootroot0000000000000052 comment=0a57366747b7694a9cf5bf40294a4c3126965bab ruby-em-socksify-0.2.1/000077500000000000000000000000001215331453400147235ustar00rootroot00000000000000ruby-em-socksify-0.2.1/.gitignore000066400000000000000000000000411215331453400167060ustar00rootroot00000000000000pkg/* *.gem .bundle Gemfile.lock ruby-em-socksify-0.2.1/.rspec000066400000000000000000000000001215331453400160260ustar00rootroot00000000000000ruby-em-socksify-0.2.1/Gemfile000066400000000000000000000000461215331453400162160ustar00rootroot00000000000000source "http://rubygems.org" gemspec ruby-em-socksify-0.2.1/README.md000066400000000000000000000034031215331453400162020ustar00rootroot00000000000000# EM-Socksify: Transparent SOCKS support for any EventMachine protocol Dealing with SOCKS proxies is pain. EM-Socksify provides a simple shim to setup & negotiate a SOCKS5 connection for any EventMachine protocol. To add SOCKS support, all you have to do is include the module and provide your destination address. ## Example: Routing HTTP request via SOCKS5 proxy ```ruby class Handler < EM::Connection include EM::Socksify def connection_completed socksify('google.ca', 80) do send_data "GET / HTTP/1.1\r\nConnection:close\r\nHost: google.ca\r\n\r\n" end end def receive_data(data) p data end end EM.run do EventMachine.connect SOCKS_HOST, SOCKS_PORT, Handler end ``` What's happening here? First, we open a raw TCP connection to the SOCKS proxy (after all, all data will flow through it). Then, we provide a Handler connection class, which includes "EM::Socksify". Once the TCP connection is established, EventMachine calls the **connection_completed** method in our handler. Here, we call socksify with the actual destination host & port (address that we actually want to get to), and the module does the rest. After you call socksify, the module temporarily intercepts your receive_data callbacks, negotiates the SOCKS connection (version, authentication, etc), and then once all is done, returns the control back to your code. Simple as that. For SOCKS proxies which require authentication, use: ```ruby socksify(destination_host, destination_port, username, password, version) ``` ## Wishlist - IPV6 support - SOCKS4 support ## Resources - [SOCKS on Wikipedia](http://en.wikipedia.org/wiki/SOCKS) - [Socksify-Ruby](https://github.com/astro/socksify-ruby) for regular Ruby TCPSocket # License (The MIT License) Copyright © 2011 Ilya Grigorik ruby-em-socksify-0.2.1/Rakefile000066400000000000000000000002201215331453400163620ustar00rootroot00000000000000require 'bundler' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' desc "Run all RSpec tests" RSpec::Core::RakeTask.new(:spec) ruby-em-socksify-0.2.1/em-socksify.gemspec000066400000000000000000000015421215331453400205230ustar00rootroot00000000000000# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "em-socksify/version" Gem::Specification.new do |s| s.name = "em-socksify" s.version = EventMachine::Socksify::VERSION s.platform = Gem::Platform::RUBY s.authors = ["Ilya Grigorik"] s.email = ["ilya@igvita.com"] s.homepage = "http://github.com/igrigorik/em-socksify" s.summary = "EventMachine SOCKSify shim: adds SOCKS support to any protocol" s.description = s.summary s.rubyforge_project = "em-socksify" s.add_dependency "eventmachine", ">= 1.0.0.beta.4" s.add_development_dependency "rspec" s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] end ruby-em-socksify-0.2.1/lib/000077500000000000000000000000001215331453400154715ustar00rootroot00000000000000ruby-em-socksify-0.2.1/lib/em-socksify.rb000066400000000000000000000001611215331453400202450ustar00rootroot00000000000000require 'eventmachine' require 'em-socksify/socksify' require 'em-socksify/errors' require 'em-socksify/socks5' ruby-em-socksify-0.2.1/lib/em-socksify/000077500000000000000000000000001215331453400177225ustar00rootroot00000000000000ruby-em-socksify-0.2.1/lib/em-socksify/errors.rb000066400000000000000000000022321215331453400215620ustar00rootroot00000000000000module EventMachine module Socksify class SOCKSError < Exception def self.define (message) Class.new(self) do def initialize super(message) end end end ServerFailure = define('general SOCKS server failure') NotAllowed = define('connection not allowed by ruleset') NetworkUnreachable = define('Network unreachable') HostUnreachable = define('Host unreachable') ConnectionRefused = define('Connection refused') TTLExpired = define('TTL expired') CommandNotSupported = define('Command not supported') AddressTypeNotSupported = define('Address type not supported') def self.for_response_code(code) case code.is_a?(String) ? code.ord : code when 1 then ServerFailure when 2 then NotAllowed when 3 then NetworkUnreachable when 4 then HostUnreachable when 5 then ConnectionRefused when 6 then TTLExpired when 7 then CommandNotSupported when 8 then AddressTypeNotSupported else self end end end end end ruby-em-socksify-0.2.1/lib/em-socksify/socks5.rb000066400000000000000000000070241215331453400214610ustar00rootroot00000000000000module EventMachine module Socksify module SOCKS5 def socks_send_handshake # Method Negotiation as described on # http://www.faqs.org/rfcs/rfc1928.html Section 3 @socks_state = :method_negotiation socks_methods.tap do |methods| send_data [5, methods.size].pack('CC') + methods.pack('C*') end end def socks_send_connect_request @socks_state = :connecting send_data [5, 1, 0].pack('CCC') if matches = @socks_target_host.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) send_data "\x01" + matches.to_a[1 .. -1].map { |s| s.to_i }.pack('CCCC') elsif @socks_target_host =~ /^[:0-9a-f]+$/ raise SOCKSError, 'TCP/IPv6 over SOCKS is not yet supported (inet_pton missing in Ruby & not supported by Tor)' else send_data [3, @socks_target_host.size, @socks_target_host].pack('CCA*') end send_data [@socks_target_port].pack('n') end def socks_send_authentication @socks_state = :authenticating send_data [5, @socks_username.length, @socks_username, @socks_password.length, @socks_password ].pack('CCA*CA*') end private # parses socks 5 server responses as specified # on http://www.faqs.org/rfcs/rfc1928.html def socks_parse_response case @socks_state when :method_negotiation return unless @socks_data.size >= 2 _, method = @socks_data.slice!(0, 2).unpack('CC') if socks_methods.include?(method) case method when 0 then socks_send_connect_request when 2 then socks_send_authentication end else raise SOCKSError, 'proxy did not accept method' end when :authenticating return unless @socks_data.size >= 2 socks_version, status_code = @socks_data.slice!(0, 2).unpack('CC') raise SOCKSError, "SOCKS version 5 not supported" unless socks_version == 5 raise SOCKSError, 'access denied by proxy' unless status_code == 0 send_socks_connect_request when :connecting return unless @socks_data.size >= 2 socks_version, status_code = @socks_data.slice(0, 2).unpack('CC') raise SOCKSError, "SOCKS version #{socks_version} is not 5" unless socks_version == 5 raise SOCKSError.for_response_code(status_code) unless status_code == 0 min_size = @socks_data[3].ord == 3 ? 5 : 4 return unless @socks_data.size >= min_size size = case @socks_data[3].ord when 1 then 4 when 3 then @socks_data[4].ord when 4 then 16 else raise SOCKSError.for_response_code(@socks_data[3]) end return unless @socks_data.size >= (min_size + size) bind_addr = @socks_data.slice(min_size, size) ip = case @socks_data[3].ord when 1 then bind_addr.bytes.to_a.join('.') when 3 then bind_addr when 4 then # TODO: ??? end socks_unhook(ip) end rescue Exception => e @socks_deferrable.fail(e) end def socks_methods methods = [] methods << 2 if !@socks_username.nil? # 2 => Username/Password Authentication methods << 0 # 0 => No Authentication Required methods end end end end ruby-em-socksify-0.2.1/lib/em-socksify/socksify.rb000066400000000000000000000016761215331453400221130ustar00rootroot00000000000000module EventMachine module Socksify def socksify(host, port, username = nil, password = nil, version = 5, &blk) @socks_target_host = host @socks_target_port = port @socks_username = username @socks_password = password @socks_version = version @socks_data = '' socks_hook socks_send_handshake @socks_deferrable = DefaultDeferrable.new @socks_deferrable.callback(&blk) if blk @socks_deferrable end def socks_hook if @socks_version == 5 extend SOCKS5 else raise ArgumentError, 'SOCKS version unsupported' end class << self alias receive_data socks_receive_data end end def socks_unhook(ip = nil) class << self remove_method :receive_data end @socks_deferrable.succeed(ip) end def socks_receive_data(data) @socks_data << data socks_parse_response end end end ruby-em-socksify-0.2.1/lib/em-socksify/version.rb000066400000000000000000000001021215331453400217250ustar00rootroot00000000000000module EventMachine module Socksify VERSION = "0.2.1" end end ruby-em-socksify-0.2.1/metadata.yml000066400000000000000000000041511215331453400172270ustar00rootroot00000000000000--- !ruby/object:Gem::Specification name: em-socksify version: !ruby/object:Gem::Version version: 0.2.1 prerelease: platform: ruby authors: - Ilya Grigorik autorequire: bindir: bin cert_chain: [] date: 2012-08-11 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: eventmachine requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 1.0.0.beta.4 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 1.0.0.beta.4 - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' description: ! 'EventMachine SOCKSify shim: adds SOCKS support to any protocol' email: - ilya@igvita.com executables: [] extensions: [] extra_rdoc_files: [] files: - .gitignore - .rspec - Gemfile - README.md - Rakefile - em-socksify.gemspec - lib/em-socksify.rb - lib/em-socksify/errors.rb - lib/em-socksify/socks5.rb - lib/em-socksify/socksify.rb - lib/em-socksify/version.rb - spec/helper.rb - spec/socksify_spec.rb homepage: http://github.com/igrigorik/em-socksify licenses: [] post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: em-socksify rubygems_version: 1.8.24 signing_key: specification_version: 3 summary: ! 'EventMachine SOCKSify shim: adds SOCKS support to any protocol' test_files: - spec/helper.rb - spec/socksify_spec.rb has_rdoc: ruby-em-socksify-0.2.1/spec/000077500000000000000000000000001215331453400156555ustar00rootroot00000000000000ruby-em-socksify-0.2.1/spec/helper.rb000066400000000000000000000001011215331453400174510ustar00rootroot00000000000000require 'rubygems' require 'bundler/setup' require 'em-socksify'ruby-em-socksify-0.2.1/spec/socksify_spec.rb000066400000000000000000000011761215331453400210530ustar00rootroot00000000000000require 'helper' describe EventMachine do it "should negotiate a socks connection" do class Handler < EM::Connection include EM::Socksify def connection_completed socksify('google.ca', 80) do |ip| send_data "GET / HTTP/1.1\r\nConnection:close\r\nHost: google.ca\r\n\r\n" end end def receive_data(data) @received ||= '' @received << data end def unbind @received.size.should > 0 @received[0,4].should == 'HTTP' EM.stop end end EM.run do EventMachine.connect '127.0.0.1', 8080, Handler end end end