# =XMPP4R - XMPP Library for Ruby # License:: Ruby's license (see the LICENSE file) or GNU GPL, at your option. # Website::http://home.gna.org/xmpp4r/ module Jabber module Bytestreams ## # The SOCKS5BytestreamsServer is an implementation of a SOCKS5 server. # # You can use it if you're reachable by your SOCKS5Bytestreams peers, # thus avoiding use of an external proxy. # # ==Usage: # * Instantiate with an unfirewalled port # * Add your external IP addresses with SOCKS5BytestreamsServer#add_address # * Once you've got an *outgoing* SOCKS5BytestreamsInitiator, do # SOCKS5BytestreamsInitiator#add_streamhost(my_socks5bytestreamsserver) # *before* you do SOCKS5BytestreamsInitiator#open class SOCKS5BytestreamsServer ## # Start a local SOCKS5BytestreamsServer # # Will start to listen on the given TCP port and # accept new peers # port:: [Fixnum] TCP port to listen on # listen_on:: [String] Optional address for the server socket to listen on (i.e. '0.0.0.0' or '::') def initialize(port, listen_on=nil) @port = port @addresses = [] @peers = [] @peers_lock = Mutex.new if listen_on socket = TCPServer.new(listen_on, port) else socket = TCPServer.new(port) end Thread.new { loop { peer = SOCKS5BytestreamsPeer.new(socket.accept) Thread.new { begin peer.start rescue Jabber::debuglog("SOCKS5 BytestreamsServer: Error accepting peer: #{$!}") end } @peers_lock.synchronize { @peers << peer } } } end ## # Find the socket a peer is associated to # # This method also performs some housekeeping, ie. removing # peers with closed sockets. # addr:: [String] Address like SOCKS5Bytestreams#stream_address # result:: [TCPSocker] or [nil] def peer_sock(addr) res = nil @peers_lock.synchronize { removes = [] @peers.each { |peer| if peer.socket and peer.socket.closed? # Queue peers with closed socket for removal removes << peer elsif peer.address == addr and res.nil? res = peer.socket end # If we sent multiple addresses of our own, clients may # connect multiple times. DO NOT close any other connections # here. These may belong to other concurrent bytestreams, # believe that the peer will close any unneeded sockets # which will then be picked up by the next call to peer_sock. } # If we sent multiple addresses of our own, clients may # connect multiple times. Close these connections here. @peers.delete_if { |peer| if removes.include? peer peer.socket.close rescue IOError true else false end } } res end ## # Add an external IP address # # This is a must-have, as SOCKS5BytestreamsInitiator must inform # the target where to connect def add_address(address) @addresses << address end ## # Iterate through all configured addresses, # yielding SOCKS5BytestreamsServerStreamHost # instances, which should be passed to # SOCKS5BytestreamsInitiator#add_streamhost # # This will be automatically invoked if you pass an instance # of SOCKS5BytestreamsServer to # SOCKS5BytestreamsInitiator#add_streamhost # my_jid:: [JID] My Jabber-ID def each_streamhost(my_jid, &block) @addresses.each { |address| yield SOCKS5BytestreamsServerStreamHost.new(self, my_jid, address, @port) } end end ## # A subclass of StreamHost which possesses a # server attribute, to let SOCKS5BytestreamsInitiator # know this is the local SOCKS5BytestreamsServer class SOCKS5BytestreamsServerStreamHost < StreamHost attr_reader :server def initialize(server, jid=nil, host=nil, port=nil) super(jid, host, port) @server = server end end ## # This class will be instantiated by SOCKS5BytestreamsServer # upon accepting a new connection class SOCKS5BytestreamsPeer attr_reader :address, :socket ## # Initialize a new peer # socket:: [TCPSocket] def initialize(socket) @socket = socket Jabber::debuglog("SOCKS5 BytestreamsServer: accepted peer #{@socket.peeraddr[2]}:#{@socket.peeraddr[1]}") end ## # Start handshake process def start auth_ver = @socket.getc if auth_ver != 5 # Unsupported version @socket.close return end auth_nmethods = @socket.getc auth_methods = @socket.read(auth_nmethods) unless auth_methods.index("\x00") # Client won't accept no authentication @socket.write("\x05\xff") @socket.close return end @socket.write("\x05\x00") Jabber::debuglog("SOCKS5 BytestreamsServer: peer #{@socket.peeraddr[2]}:#{@socket.peeraddr[1]} authenticated") req = @socket.read(4) if req != "\x05\x01\x00\x03" # Unknown version, command, reserved, address-type @socket.close return end req_addrlen = @socket.getc req_addr = @socket.read(req_addrlen) req_port = @socket.read(2) if req_port != "\x00\x00" # Port is not 0 @socket.write("\x05\x01") @socket.close return end @socket.write("\x05\x00\x00\x03#{req_addrlen.chr}#{req_addr}\x00\x00") Jabber::debuglog("SOCKS5 BytestreamsServer: peer #{@socket.peeraddr[2]}:#{@socket.peeraddr[1]} connected for #{req_addr}") @address = req_addr end end end end