Skip to content
Permalink
Browse files
Refactoring of SOCKSClient to not depend on qbuf.
* Adapt unittests to support the changes in design
* XXX disable some unittests that are no longer needed
  • Loading branch information
hellais committed Dec 3, 2012
1 parent fbd0a68 commit 9fb250f712a013fbafb843285795524b868c1688
Showing with 130 additions and 138 deletions.
  1. +10 −2 txsocksx/auth.py
  2. +72 −49 txsocksx/client.py
  3. +3 −0 txsocksx/errors.py
  4. +20 −10 txsocksx/parser.py
  5. +20 −72 txsocksx/test/test_client.py
  6. +5 −5 txsocksx/test/test_parser.py
@@ -5,13 +5,16 @@ class Anonymous(object):
"""
( 0 )
"""
method = '\x00'
def negotiate(self, proto):
pass
self.negotiated = True
return defer.succeed(None)

class GSSAPI(object):
"""
( 1 )
"""
method = '\x01'
def negotiate(self, proto):
raise NotImplemented

@@ -22,12 +25,17 @@ class UsernamePassword(object):
"""
( 2 )
"""
method = '\x02'
def __init__(self, uname, passwd):
self.uname = uname
self.passwd = passwd

def negotiate(self, proto):
proto.transport.write(
'\x01'
self.method
+ chr(len(self.uname)) + self.uname
+ chr(len(self.passwd)) + self.passwd)
# XXX implement the reading of the response and make sure
# authentication suceeded
return defer.succeed(None)

@@ -1,72 +1,95 @@
import struct

from qbuf.support.twisted import MultiBufferer, MODE_RAW
from twisted.internet import protocol, defer, interfaces
from twisted.python import failure
from zope.interface import implements

import txsocksx.constants as c, txsocksx.errors as e
from txsocksx import constants as c
import txsocksx.errors as e

from txsocksx import auth

def socks_host(host):
return chr(c.ATYP_DOMAINNAME) + chr(len(host)) + host
from txsocksx.parser import SOCKSGrammar

def shortToBytes(i):
return chr(i >> 8) + chr(i & 0xff)

class Socks5ClientTransport(object):
class SOCKS5ClientTransport(object):
def __init__(self, wrappedClient):
self.wrappedClient = wrappedClient
self.transport = self.wrappedClient.transport

def __getattr__(self, attr):
return getattr(self.transport, attr)

class Socks5Client(MultiBufferer):
class SOCKS5Client(protocol.Protocol):
implements(interfaces.ITransport)

otherProtocol = None
def __init__(self):
self._state = 'ServerVersionMethod'

def connectionMade(self):
d = self.readReply()
@d.addErrback
def _eb(reason):
self.factory.proxyConnectionFailed(reason)
self.close()
return d

@defer.inlineCallbacks
def readReply(self):
methodMap = dict((m.method, m) for m in self.factory.authMethods)
self.writeVersionMethod()

def writeVersionMethod(self):
"""
This creates:
ver octet:nmethods octet{1, 255}:methods
"""
supported_methods = [m.method for m in self.factory.authMethods]

message = struct.pack('!BB', c.VER_SOCKS5,
len(supported_methods))
message += ''.join(supported_methods)

self.transport.write(message)

def writeRequest(self, result, cmd=c.CMD_CONNECT):
"""
This creates:
clientRequestMessage =
ver cmd rsv SOCKSAddress port
"""
# XXX-Security audit makeGrammar
message = SOCKSGrammar(self.factory.host)
req = struct.pack('!BBB', c.VER_SOCKS5, cmd, 0)
self.transport.write(
struct.pack('!BB', c.VER_SOCKS5, len(methodMap))
+ ''.join(methodMap))
method, = yield self.unpack('!xc')
if method not in methodMap:
raise e.MethodsNotAcceptedError('no method proprosed was accepted',
methodMap.keys(), method)
yield methodMap[method].negotiate(self)
data = struct.pack('!BBB', c.VER_SOCKS5, c.CMD_CONNECT, c.RSV)
port = struct.pack('!H', self.factory.port)
self.transport.write(data + socks_host(self.factory.host) + port)
status, address_type = yield self.unpack('!xBxB')
if status != c.SOCKS5_GRANTED:
raise e.ConnectionError('connection rejected by SOCKS server',
status,
e.socks5ErrorMap.get(status, status))

# Discard the host and port data from the server.
if address_type == c.ATYP_IPV4:
yield self.read(4)
elif address_type == c.ATYP_DOMAINNAME:
host_length, = yield self.unpack('!B')
yield self.read(host_length)
elif address_type == c.ATYP_IPV6:
yield self.read(16)
yield self.read(2)

self.setMode(MODE_RAW)
req + \
message.hostToSOCKSAddress() + \
shortToBytes(self.factory.port)
)
self._state = 'ServerReply'

def readServerVersionMethod(self, message):
ver, method = message.serverVersionMethod()
if method not in self.factory.authMethods:
raise e.MethodsNotAcceptedError(
'no method proprosed was accepted',
self.factory.authMethods, method)
else:
auth_method = method()
d = defer.maybeDeferred(auth_method.negotiate, self)
d.addCallback(self.writeRequest)

def readServerReply(self, message):
status, address, port = message.serverReply()
if status != 0:
raise status
self.factory.proxyConnectionEstablished(self)

def dataReceived(self, data):
# XXX-Security audit makeGrammar
message = SOCKSGrammar(data)

current_state_method = getattr(self, 'read' + self._state)
d = defer.maybeDeferred(current_state_method,
message)
d.addErrback(self.factory.proxyConnectionFailed)

def proxyEstablished(self, other):
self.otherProtocol = other
other.makeConnection(Socks5ClientTransport(self))
other.makeConnection(SOCKS5ClientTransport(self))

def rawDataReceived(self, data):
# There really is no reason for this to get called; we shouldn't be in
@@ -81,8 +104,8 @@ def connectionLost(self, reason):
self.factory.proxyConnectionFailed(
failure.Failure(e.ConnectionLostEarly()))

class Socks5ClientFactory(protocol.ClientFactory):
protocol = Socks5Client
class SOCKS5ClientFactory(protocol.ClientFactory):
protocol = SOCKS5Client

def __init__(self, host, port, proxiedFactory, authMethods):
self.host = host
@@ -104,17 +127,17 @@ def proxyConnectionEstablished(self, proxyProtocol):
proxyProtocol.proxyEstablished(proto)
self.deferred.callback(proto)

class Socks5ClientEndpoint(object):
class SOCKS5ClientEndpoint(object):
implements(interfaces.IStreamClientEndpoint)

def __init__(self, host, port, proxyEndpoint, authMethods=(auth.Anonymous(),)):
def __init__(self, host, port, proxyEndpoint, authMethods=(auth.Anonymous,)):
self.host = host
self.port = port
self.proxyEndpoint = proxyEndpoint
self.authMethods = authMethods

def connect(self, fac):
proxyFac = Socks5ClientFactory(self.host, self.port, fac, self.authMethods)
proxyFac = SOCKS5ClientFactory(self.host, self.port, fac, self.authMethods)
self.proxyEndpoint.connect(proxyFac)
# XXX: maybe use the deferred returned here? need to more different
# ways/times a connection can fail before connectionMade is called.
@@ -1,6 +1,9 @@
from twisted.internet import error
import txsocksx.constants as c

class ParsingError(Exception):
pass

class SOCKSError(Exception):
pass

@@ -7,6 +7,7 @@
# XXX probably move these to another grammar and inherit from it
byte = anything:byte -> ord(byte)
short = byte:high byte:low -> (high << 8) | low
hexdigit = :x ?(x in '0123456789abcdefABCDEF') -> x
byteToIntStr = anything:b
-> str(ord(b))
@@ -29,8 +30,10 @@
# < (domainLabel '.'?)* >
# XXX make this stricter
DomainName =
byte:len <anything{len}>
DomainName = letterOrDigit | '-' | '.'
SOCKSDomainName =
byte:len <DomainName{len}>
# Below are SOCKS specific messages
ver = ('\x05' -> 5)
@@ -41,22 +44,24 @@
SOCKSAddress = ('\x01' IPV4AddrBytes:addr
-> addr)
| ('\x03' IPV6AddrBytes:addr
-> addr)
| ('\x04' DomainName:domain
| ('\x03' SOCKSDomainName:domain
-> domain)
| ('\x04' IPV6AddrBytes:addr
-> addr)
hostToSOCKSAddress =
( IPV4AddrBytes:addr
( IPV4AddrStr:addr
-> '\x01' + addr )
| ( IPV6AddrBytes:addr
-> '\x03' + addr )
| ( <DomainName*>:addr
-> '\x03' + chr(len(addr)) + addr )
| ( DomainName:addr
| ( IPV6AddrStr:addr
-> '\x04' + addr )
port = short:p -> int(p)
# The Client version identified/method selection message
@@ -100,3 +105,8 @@
{"e": errors, "a": auth}
)

# XXX how do I do the equivalent of the above and generate the grammar to a
# file?
#parsley.moduleFromGrammar(socks_grammar, 'SOCKSGrammar',
# object, {"e": error, "a": auth})

0 comments on commit 9fb250f

Please sign in to comment.