[Pkg-privacy-commits] [obfsproxy] 257/353: More changes based on feedback

Ximin Luo infinity0 at moszumanska.debian.org
Sat Aug 22 13:02:07 UTC 2015


This is an automated email from the git hooks/post-receive script.

infinity0 pushed a commit to branch master
in repository obfsproxy.

commit 7924d1d2b04452b2639cd6fe6c8bb15d64f1be3e
Author: Yawning Angel <yawning at schwanenlied.me>
Date:   Wed Mar 5 23:08:33 2014 +0000

    More changes based on feedback
    
     * Use a wrapper class to isolate the scary struct.pack/unpack calls
     * Refactor the code to look like twisted.protocols.socks
     * Support DOMAINNAME, though it's untested
---
 obfsproxy/network/socks.py  | 396 +++++-----------------------------
 obfsproxy/network/socks5.py | 515 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 571 insertions(+), 340 deletions(-)

diff --git a/obfsproxy/network/socks.py b/obfsproxy/network/socks.py
index 01f04bb..b55b7bc 100644
--- a/obfsproxy/network/socks.py
+++ b/obfsproxy/network/socks.py
@@ -1,75 +1,15 @@
-from twisted.internet import reactor, protocol, error
+import csv
+
+from twisted.internet import reactor, protocol
 
 import obfsproxy.common.log as logging
 import obfsproxy.network.network as network
+import obfsproxy.network.socks5 as socks5
 import obfsproxy.transports.base as base
 
-import csv
-import socket
-import struct
 
 log = logging.get_obfslogger()
 
-"""
-SOCKS5 Server:
-
-This is a SOCKS5 server written specifically for Tor Pluggable Transports.  It
-is compliant with RFC 1928 and RFC 1929 with the following exceptions.
-
- * GSSAPI Authentication is not and will not ever be supported.
- * The SOCKS5 CMDs BIND and UDP ASSOCIATE are not supported.
- * The SOCKS5 ATYP DOMAINNAME is not supported.  This is a intentional design
-   choice.  While it would be trivial to support this, Pluggable Transports
-   hitting up DNS is a DNS leak, and it is Tor's problem to pass acceptable
-   addresses.
-"""
-
-#
-# SOCKS Server States
-#
-_SOCKS_ST_READ_METHODS = 1
-_SOCKS_ST_AUTHENTICATING = 2
-_SOCKS_ST_READ_REQUEST = 3
-_SOCKS_ST_CONNECTING = 4
-_SOCKS_ST_ESTABLISHED = 5
-
-#
-# SOCKS5 Constants
-#
-_SOCKS_VERSION = 0x05
-_SOCKS_AUTH_NO_AUTHENTICATION_REQUIRED = 0x00
-#_SOCKS_AUTH_GSSAPI = 0x01
-_SOCKS_AUTH_USERNAME_PASSWORD = 0x02
-_SOCKS_AUTH_NO_ACCEPTABLE_METHODS = 0xFF
-_SOCKS_CMD_CONNECT = 0x01
-#_SOCKS_CMD_BIND = 0x02
-#_SOCKS_CMD_UDP_ASSOCIATE = 0x03
-_SOCKS_ATYP_IP_V4 = 0x01
-_SOCKS_ATYP_DOMAINNAME = 0x03
-_SOCKS_ATYP_IP_V6 = 0x04
-_SOCKS_RSV = 0x00
-_SOCKS_RFC1929_VER = 0x01
-_SOCKS_RFC1929_SUCCESS = 0x00
-_SOCKS_RFC1929_FAIL = 0x01
-
-class SOCKSv5Reply(object):
-    """
-    SOCKS reply codes
-    """
-
-    __slots__ = ['Succeded', 'GeneralFailure', 'ConnectionNotAllowed',
-                 'NetworkUnreachable', 'HostUnreachable', 'ConnectionRefused',
-                 'TTLExpired', 'CommandNotSupported', 'AddressTypeNotSupported']
-
-    Succeeded = 0x00
-    GeneralFailure = 0x01
-    ConnectionNotAllowed = 0x02
-    NetworkUnreachable = 0x03
-    HostUnreachable = 0x04
-    ConnectionRefused = 0x05
-    TTLExpired = 0x06
-    CommandNotSupported = 0x07
-    AddressTypeNotSupported = 0x08
 
 def _split_socks_args(args_str):
     """
@@ -79,200 +19,83 @@ def _split_socks_args(args_str):
     """
     return csv.reader([args_str], delimiter=';', escapechar='\\').next()
 
-class SOCKSv5Outgoing(network.GenericProtocol):
+
+class SOCKSv5Outgoing(socks5.SOCKSv5Outgoing, network.GenericProtocol):
     """
-    Represents a downstream connection from our SOCKS server to a remote peer.
+    Represents a downstream connection from the SOCKS server to the
+    destination.
+
+    It subclasses socks5.SOCKSv5Outgoing, so that data can be passed to the
+    pluggable transport before proxying.
 
     Attributes:
-    circuit: The curcuit object this connection belongs to.
-    buffer: Buffer that holds data waiting to be processed.
+    circuit: The circuit this connection belongs to.
+    buffer: Buffer that holds data that can't be proxied right
+            away. This can happen because the circuit is not yet
+            complete, or because the pluggable transport needs more
+            data before deciding what to do.
     """
 
     name = None
 
-    _socks = None
+    def __init__(self, socksProtocol):
+        """
+        Constructor.
 
-    def __init__(self, socks):
+        'socksProtocol' is a 'SOCKSv5Protocol' object.
+        """
         self.name = "socks_down_%s" % hex(id(self))
-        self._socks = socks
+        self.socks = socksProtocol
 
-        network.GenericProtocol.__init__(self, socks.circuit)
+        network.GenericProtocol.__init__(self, socksProtocol.circuit)
+        return super(SOCKSv5Outgoing, self).__init__(socksProtocol)
 
     def connectionMade(self):
-        self._socks._other_conn = self
-        self._socks.setup_circuit()
+        self.socks.otherConn = self
+        self.socks.set_up_circuit()
+
         # XXX: The transport should do this after handshaking
-        self._socks.send_reply(SOCKSv5Reply.Succeeded)
+        self.socks.sendReply(socks5.SOCKSv5Reply.Succeeded)
 
     def dataReceived(self, data):
-        log.debug("%s: Received %d bytes." % (self.name, len(data)))
+        log.debug("%s: Recived %d bytes." % (self.name, len(data)))
 
         assert self.circuit.circuitIsReady()
         self.buffer.write(data)
         self.circuit.dataReceived(self.buffer, self)
 
-class SOCKSv5Protocol(network.GenericProtocol):
-    """
-    Represents an upstream connection from a SOCKS client to our SOCKS server.
 
-    Attributes:
-    circuit: The curcuit object this connection belongs to.
-    buffer: Buffer that holds data waiting to be processed.
+class SOCKSv5Protocol(socks5.SOCKSv5Protocol, network.GenericProtocol):
     """
+    Represents an upstream connection from a SOCKS client to our SOCKS
+    server.
 
-    name = None
-
-    _state = None
-    _auth_method = None
-    _other_conn = None
+    It overrides socks5.SOCKSv5Protocol because py-obfsproxy's connections need
+    to have a circuit and obfuscate traffic before proxying it.
+    """
 
     def __init__(self, circuit):
         self.name = "socks_up_%s" % hex(id(self))
-        self._state = _SOCKS_ST_READ_METHODS
 
         network.GenericProtocol.__init__(self, circuit)
+        socks5.SOCKSv5Protocol.__init__(self)
+
+        # Disable the unsupported commands
+        self.ACCEPTABLE_CMDS.remove(socks5._SOCKS_CMD_BIND)
+        self.ACCEPTABLE_CMDS.remove(socks5._SOCKS_CMD_UDP_ASSOCIATE)
 
     def connectionLost(self, reason):
         network.GenericProtocol.connectionLost(self, reason)
 
-    def dataReceived(self, data):
-        log.debug("%s: Received %d bytes." % (self.name, len(data)))
+    def processEstablishedData(self, data):
+        assert self.circuit.circuitIsReady()
         self.buffer.write(data)
+        self.circuit.dataReceived(self.buffer, self)
 
-        if self._state == _SOCKS_ST_READ_METHODS:
-            self._process_method_select()
-        elif self._state == _SOCKS_ST_AUTHENTICATING:
-            self._process_authentication()
-        elif self._state == _SOCKS_ST_READ_REQUEST:
-            self._process_request()
-        elif self._state == _SOCKS_ST_CONNECTING:
-            log.warning("%s: Client sent data before receiving response" % self.name)
-            self.transport.loseConnection()
-        elif self._state == _SOCKS_ST_ESTABLISHED:
-            assert self.circuit.circuitIsReady()
-            self.circuit.dataReceived(self.buffer, self)
-        else:
-            log.warning("%s: Invalid state in SOCKS5 Server: '%d'" % (self.name, self._state))
-            self.transport.loseConnection()
-
-    def _process_method_select(self):
-        """
-        Parse Version Identifier/Method Selection Message, and send a response
-        """
-
-        msg = self.buffer.peek()
-        if len(msg) < 2:
-            return
-        ver, nmethods = struct.unpack("BB", msg[0:2])
-        if ver != _SOCKS_VERSION:
-            log.warning("%s: Invalid SOCKS version: '%d'" % (self.name, ver))
-            self.transport.loseConnection()
-            return
-        if nmethods == 0:
-            log.warning("%s: No Authentication method present" % self.name)
-            self.transport.loseConnection()
-            return
-
-        #
-        # Ensure that the entire message is present, and find the "best"
-        # authentication method.
-        #
-        # In a perfect world, the transport will specify that authentication
-        # is required, but since tor will do the work for us, this will
-        # suffice.
-        #
-
-        if len(msg) < 2 + nmethods:
-            return
-        methods = msg[2:2 + nmethods]
-        if chr(_SOCKS_AUTH_USERNAME_PASSWORD) in methods:
-            log.debug("%s: Username/Password Authentication" % self.name)
-            self._auth_method = _SOCKS_AUTH_USERNAME_PASSWORD
-        elif chr(_SOCKS_AUTH_NO_AUTHENTICATION_REQUIRED) in methods:
-            log.debug("%s: No Authentication Required" % self.name)
-            self._auth_method = _SOCKS_AUTH_NO_AUTHENTICATION_REQUIRED
-        else:
-            log.warning("%s: No Compatible Authentication method" % self.name)
-            self._auth_method = _SOCKS_AUTH_NO_ACCEPTABLE_METHODS
-
-        # Ensure there is no trailing garbage
-        self.buffer.drain(2 + nmethods)
-        if len(self.buffer) > 0:
-            log.warning("%s: Peer sent trailing garbage after method select" % self.name)
-            self.transport.loseConnection()
-            return
-
-        # Send Method Selection Message
-        self.transport.write(struct.pack("BB", _SOCKS_VERSION, self._auth_method))
-        if self._auth_method == _SOCKS_AUTH_NO_ACCEPTABLE_METHODS:
-            self.transport.loseConnection()
-            return
-
-        self._state = _SOCKS_ST_AUTHENTICATING
-
-    def _process_authentication(self):
-        """
-        Handle client data when authenticating
-        """
-
-        if self._auth_method == _SOCKS_AUTH_NO_AUTHENTICATION_REQUIRED:
-            self._state = _SOCKS_ST_READ_REQUEST
-            self._process_request()
-        elif self._auth_method == _SOCKS_AUTH_USERNAME_PASSWORD:
-            self._process_rfc1929_request()
-        else:
-            log.warning("%s: Peer sent data when we failed to negotiate auth" % (self.name))
-            self.buffer.drain()
-            self.transport.loseConnection()
-
-    def _process_rfc1929_request(self):
-        """
-        Handle RFC1929 Username/Password authentication requests
-        """
-
-        msg = self.buffer.peek()
-        if len(msg) < 2:
-            return
-
-        # Parse VER, ULEN
-        ver, ulen = struct.unpack("BB", msg[0:2])
-        if ver != _SOCKS_RFC1929_VER:
-            log.warning("%s: Invalid RFC1929 version: '%d'" % (self.name, ver))
-            self._send_rfc1929_reply(False)
-            return
-        if ulen == 0:
-            log.warning("%s: Username length is 0" % self.name)
-            self._send_rfc1929_reply(False)
-            return
-        if len(msg) < 2 + ulen:
-            return
-        uname = msg[2:2 + ulen]
-        if len(msg) < 2 + ulen + 1:
-            return
-        plen = struct.unpack("B", msg[2 + ulen])[0]
-        if len(msg) < 2 + ulen + 1 + plen:
-            return
-        if plen == 0:
-            log.warning("%s: Password length is 0" % self.name)
-            self._send_rfc1929_reply(False)
-            return
-        passwd = msg[2 + ulen + 1:2 + ulen + 1 + plen]
-
-        # Ensure there is no trailing garbage
-        self.buffer.drain(2 + ulen + 1 + plen)
-        if len(self.buffer) > 0:
-            log.warning("%s: Peer sent trailing garbage after RFC1929 auth" % self.name)
-            self.transport.loseConnection()
-            return
-
-        if self.process_rfc1929_auth(uname, passwd) == True:
-            self._send_rfc1929_reply(True)
-        else:
-            self._send_rfc1929_reply(False)
-
-    def process_rfc1929_auth(self, uname, passwd):
+    def processRfc1929Auth(self, uname, passwd):
         """
-        Handle the RFC1929 Username/Password received from the client
+        Handle the Pluggable Transport variant of RFC1929 Username/Password
+        authentication.
         """
 
         # The Tor PT spec jams the per session arguments into the UNAME/PASSWD
@@ -306,136 +129,29 @@ class SOCKSv5Protocol(network.GenericProtocol):
 
         return True
 
-    def _send_rfc1929_reply(self, success):
-        """
-        Send a RFC1929 Username/Password Authentication response
+    def connectClass(self, addr, port, klass, *args):
         """
+        Instantiate the outgoing connection.
 
-        if success == True:
-            self.transport.write(struct.pack("BB", 1, _SOCKS_RFC1929_SUCCESS))
-            self._state = _SOCKS_ST_READ_REQUEST
-        else:
-            self.transport.write(struct.pack("BB", 1, _SOCKS_RFC1929_FAIL))
-            self.transport.loseConnection()
-
-    def _process_request(self):
-        """
-        Parse the client request, and setup the TCP/IP connection
+        This is overriden so that our sub-classed SOCKSv5Outgoing gets created.
         """
 
-        msg = self.buffer.peek()
-        if len(msg) < 4:
-            return
-
-        ver, cmd, rsv, atyp = struct.unpack("BBBB", msg[0:4])
-        if ver != _SOCKS_VERSION:
-            log.warning("%s: Invalid SOCKS version: '%d'" % (self.name, ver))
-            self.send_reply(SOCKSv5Reply.GeneralFailure)
-            return
-        if cmd != _SOCKS_CMD_CONNECT:
-            log.warning("%s: Invalid SOCKS command: '%d'" % (self.name, cmd))
-            self.send_reply(SOCKSv5Reply.CommandNotSupported)
-            return
-        if rsv != _SOCKS_RSV:
-            log.warning("%s: Invalid SOCKS RSV: '%d'" % (self.name, rsv))
-            self.send_reply(SOCKSv5Reply.GeneralFailure)
-            return
-
-        # Deal with the address
-        addr = None
-        if atyp == _SOCKS_ATYP_IP_V4:
-            if len(msg) < 4 + 4 + 2:
-                return
-            addr = socket.inet_ntoa(msg[4:8])
-            self.buffer.drain(4 + 4)
-        elif atyp == _SOCKS_ATYP_IP_V6:
-            if len(msg) < 4 + 16 + 2:
-                return
-            try:
-                addr = socket.inet_ntop(socket.AF_INET6, msg[4:20])
-            except:
-                log.warning("%s: Failed to parse IPv6 address" % self.name)
-                self.send_reply(SOCKSv5Reply.AddressTypeNotSupported)
-                return
-            self.buffer.drain(4 + 16)
-        elif atyp == _SOCKS_ATYP_DOMAINNAME:
-            log.warning("%s: Domain Name address type is not supported" % self.name)
-            self.send_reply(SOCKSv5Reply.AddressTypeNotSupported)
-            return
-        else:
-            log.warning("%s: Invalid SOCKS address type: '%d'" % (self.name, atyp))
-            self.send_reply(SOCKSv5Reply.AddressTypeNotSupported)
-            return
-
-        # Deal with the port
-        port = struct.unpack("!H", self.buffer.read(2))[0]
-
-        # Ensure there is no trailing garbage
-        if len(self.buffer) > 0:
-            log.warning("%s: Peer sent trailing garbage after request" % self.name)
-            self.send_reply(SOCKSv5Reply.GeneralFailure)
-            return
-
-        # Connect -> addr/port
-        d = protocol.ClientCreator(reactor, SOCKSv5Outgoing, self).connectTCP(addr, port)
-        d.addErrback(self._handle_connect_failure)
-
-        self._state = _SOCKS_ST_CONNECTING
-
-    def _handle_connect_failure(self, failure):
-        log.warning("%s: Failed to connect to peer: %s" % (self.name, failure))
-
-        # Map common twisted errors to SOCKS error codes
-        if failure.type == error.NoRouteError:
-            self.send_reply(SOCKSv5Reply.NetworkUnreachable)
-        elif failure.type == error.ConnectionRefusedError:
-            self.send_reply(SOCKSv5Reply.ConnectionRefused)
-        elif failure.type == error.TCPTimedOutError or failure.type == error.TimeoutError:
-            self.send_reply(SOCKSv5Reply.TTLExpired)
-        elif failure.type == error.UnsupportedAddressFamily:
-            self.send_reply(SOCKSv5Reply.AddressTypeNotSupported)
-        else:
-            self.send_reply(SOCKSv5Reply.GeneralFailure)
-
-    def setup_circuit(self):
-        assert self._other_conn
-        self.circuit.setDownstreamConnection(self._other_conn)
-        self.circuit.setUpstreamConnection(self)
+        return protocol.ClientCreator(reactor, SOCKSv5Outgoing, self).connectTCP(addr, port)
 
-    def send_reply(self, reply):
-        """
-        Send a reply to the request, and complete circuit setup
-        """
-
-        if reply == SOCKSv5Reply.Succeeded:
-            host = self.transport.getHost()
-            port = host.port
-            try:
-                raw_addr = socket.inet_aton(host.host)
-                self.transport.write(struct.pack("BBBB", _SOCKS_VERSION, reply, _SOCKS_RSV, _SOCKS_ATYP_IP_V4) + raw_addr + struct.pack("!H",port))
-            except socket.error:
-                try:
-                    raw_addr = socket.inet_pton(socket.AF_INET6, host.host)
-                    self.transport.write(struct.pack("BBBB", _SOCKS_VERSION, reply, _SOCKS_RSV, _SOCKS_ATYP_IP_V6) + raw_addr + struct.pack("!H",port))
-                except:
-                    log.warning("%s: Failed to parse bound address" % self.name)
-                    self.send_reply(SOCKSv5Reply.GeneralFailure)
-                    return
-
-            self._state = _SOCKS_ST_ESTABLISHED
-        else:
-            self.transport.write(struct.pack("!BBBBIH", _SOCKS_VERSION, reply, _SOCKS_RSV, _SOCKS_ATYP_IP_V4, 0, 0))
-            self.transport.loseConnection()
+    def set_up_circuit(self):
+        assert self.otherConn
+        self.circuit.setDownstreamConnection(self.otherConn)
+        self.circuit.setUpstreamConnection(self)
 
 class SOCKSv5Factory(protocol.Factory):
     """
-    A SOCKSv5 Factory.
+    A SOCKSv5 factory.
     """
 
     def __init__(self, transport_class, pt_config):
         # XXX self.logging = log
         self.transport_class = transport_class
-        self.pt_config  = pt_config
+        self.pt_config = pt_config
 
         self.name = "socks_fact_%s" % hex(id(self))
 
diff --git a/obfsproxy/network/socks5.py b/obfsproxy/network/socks5.py
new file mode 100644
index 0000000..7216797
--- /dev/null
+++ b/obfsproxy/network/socks5.py
@@ -0,0 +1,515 @@
+from twisted.internet import reactor, protocol, error
+from twisted.python import log
+
+from operator import methodcaller
+import socket
+import struct
+
+"""
+SOCKS5 Server:
+
+This is a SOCKS5 server.  There are many others like it but this one is mine.
+It is compliant with RFC 1928 and RFC 1929, with the following limitations:
+
+ * GSSAPI Autentication is not supported
+ * BIND/UDP_ASSOCIATE are not implemented, and will return a CommandNotSupported
+   SOCKS5 error, and close the connection.
+"""
+
+#
+# SOCKS5 Constants
+#
+_SOCKS_VERSION = 0x05
+_SOCKS_AUTH_NO_AUTHENTICATION_REQUIRED = 0x00
+_SOCKS_AUTH_GSSAPI = 0x01
+_SOCKS_AUTH_USERNAME_PASSWORD = 0x02
+_SOCKS_AUTH_NO_ACCEPTABLE_METHODS = 0xFF
+_SOCKS_CMD_CONNECT = 0x01
+_SOCKS_CMD_BIND = 0x02
+_SOCKS_CMD_UDP_ASSOCIATE = 0x03
+_SOCKS_ATYP_IP_V4 = 0x01
+_SOCKS_ATYP_DOMAINNAME = 0x03
+_SOCKS_ATYP_IP_V6 = 0x04
+_SOCKS_RSV = 0x00
+_SOCKS_RFC1929_VER = 0x01
+_SOCKS_RFC1929_SUCCESS = 0x00
+_SOCKS_RFC1929_FAIL = 0x01
+
+class SOCKSv5Reply(object):
+    """
+    SOCKSv5 reply codes
+    """
+
+    __slots__ = ['Succeded', 'GeneralFailure', 'ConnectionNotAllowed',
+                 'NetworkUnreachable', 'HostUnreachable', 'ConnectionRefused',
+                 'TTLExpired', 'CommandNotSupported', 'AddressTypeNotSupported']
+
+    Succeeded = 0x00
+    GeneralFailure = 0x01
+    ConnectionNotAllowed = 0x02
+    NetworkUnreachable = 0x03
+    HostUnreachable = 0x04
+    ConnectionRefused = 0x05
+    TTLExpired = 0x06
+    CommandNotSupported = 0x07
+    AddressTypeNotSupported = 0x08
+
+
+class SOCKSv5Outgoing(protocol.Protocol):
+
+    socks = None
+
+    def __init__(self, socks):
+        self.socks = socks
+
+    def connectionMade(self):
+        self.socks.otherConn = self
+        self.socks.send_reply(SOCKSv5Reply.Succeeded)
+
+    def connectionLost(self, reason):
+        self.socks.transport.loseConnection()
+
+    def dataReceived(self, data):
+        self.socks.log(self, data)
+        self.transport.write(data)
+
+    def write(self, data):
+        self.socks.log(self, data)
+        self.transport.write(data)
+
+
+class SOCKSv5Protocol(protocol.Protocol):
+    """
+    Represents an upstream connection from a SOCKS client to our SOCKS server.
+    """
+
+    buf = None
+    state = None
+    authMethod = None
+    otherConn = None
+
+    # State values
+    ST_INIT = 0
+    ST_READ_METHODS = 1
+    ST_AUTHENTICATING = 2
+    ST_READ_REQUEST = 3
+    ST_CONNECTING = 4
+    ST_ESTABLISHED = 5
+
+    # Authentication methods
+    ACCEPTABLE_AUTH_METHODS = [
+        _SOCKS_AUTH_USERNAME_PASSWORD,
+        _SOCKS_AUTH_NO_AUTHENTICATION_REQUIRED
+    ]
+    AUTH_METHOD_VTABLE = {
+        _SOCKS_AUTH_USERNAME_PASSWORD: methodcaller('processRfc1929Request'),
+        _SOCKS_AUTH_NO_AUTHENTICATION_REQUIRED: methodcaller('processNoAuthRequired')
+    }
+
+    # Commands
+    ACCEPTABLE_CMDS = [
+        _SOCKS_CMD_CONNECT,
+        _SOCKS_CMD_BIND,
+        _SOCKS_CMD_UDP_ASSOCIATE
+    ]
+
+    def __init__(self, logging=None, reactor=reactor):
+        self.logging = logging
+        self.reactor = reactor
+        self.state = self.ST_INIT
+
+    def connectionMade(self):
+        self.buf = _ByteBuffer()
+        self.otherConn = None
+        self.state = self.ST_READ_METHODS
+        self.authMethod = _SOCKS_AUTH_NO_ACCEPTABLE_METHODS
+
+    def connectionLost(self, reason):
+        if self.otherConn:
+            self.otherConn.transport.loseConnection()
+
+    def dataReceived(self, data):
+        if self.state == self.ST_ESTABLISHED:
+            self.processEstablishedData(data)
+            return
+
+        self.buf.add(data)
+        if self.state == self.ST_READ_METHODS:
+            self.processMethodSelect()
+        elif self.state == self.ST_AUTHENTICATING:
+            self.processAuthentication()
+        elif self.state == self.ST_READ_REQUEST:
+            self.processRequest()
+        elif self.state == self.ST_CONNECTING:
+            # This only happens when the client is busted
+            log.msg("Client sent data before receiving response")
+            self.transport.loseConnection()
+        else:
+            log.msg("Invalid state in SOCKS5 Server: '%d'" % self.state)
+            self.transport.loseConnection()
+
+    def processEstablishedData(self, data):
+        assert self.otherConn
+        self.otherConn.write(self.buf)
+
+    def processMethodSelect(self):
+        """
+        Parse Version Identifier/Method Selection Message, and send a response
+        """
+
+        msg = self.buf.peek()
+        if len(msg) < 2:
+            return
+
+        ver = msg.get_uint8()
+        nmethods = msg.get_uint8()
+        if ver != _SOCKS_VERSION:
+            log.msg("Invalid SOCKS version: '%d'" % ver)
+            self.transport.loseConnection()
+            return
+        if nmethods == 0:
+            log.msg("No Authentication method(s) present")
+            self.transport.loseConnection()
+            return
+        if len(msg) < nmethods:
+            return
+
+        # Select the best method
+        methods = msg.get(nmethods)
+        for method in self.ACCEPTABLE_AUTH_METHODS:
+            if method in methods:
+                self.authMethod = method
+                break
+        if self.authMethod == _SOCKS_AUTH_NO_ACCEPTABLE_METHODS:
+            log.msg("No Acceptable Authentication Methods")
+            self.authMethod = _SOCKS_AUTH_NO_ACCEPTABLE_METHODS
+
+        # Ensure there is no trailing garbage
+        if len(msg) > 0:
+            log.msg("Peer sent trailing garbage after method select")
+            self.transport.loseConnection()
+            return
+        self.buf.clear()
+
+        # Send Method Selection Message
+        msg = _ByteBuffer()
+        msg.add_uint8(_SOCKS_VERSION)
+        msg.add_uint8(self.authMethod)
+        self.transport.write(str(msg))
+
+        if self.authMethod == _SOCKS_AUTH_NO_ACCEPTABLE_METHODS:
+            self.transport.loseConnection()
+            return
+
+        self.state = self.ST_AUTHENTICATING
+
+    def processAuthentication(self):
+        """
+        Handle client data when authenticating
+        """
+
+        if self.authMethod in self.AUTH_METHOD_VTABLE:
+            self.AUTH_METHOD_VTABLE[self.authMethod](self)
+        else:
+            # Should *NEVER* happen
+            log.msg("Peer sent data when we failed to negotiate auth")
+            self.buf.clear()
+            self.transport.loseConnection()
+
+    def processRfc1929Request(self):
+        """
+        Handle RFC1929 Username/Password authentication requests
+        """
+
+        msg = self.buf.peek()
+        if len(msg) < 2:
+            return
+
+        # Parse VER, ULEN
+        ver = msg.get_uint8()
+        ulen = msg.get_uint8()
+        if ver != _SOCKS_RFC1929_VER:
+            log.msg("Invalid RFC1929 version: '%d'" % ver)
+            self.sendRfc1929Reply(False)
+            return
+        if ulen == 0:
+            log.msg("Username length is 0")
+            self.sendRfc1929Reply(False)
+            return
+
+        # Process PLEN
+        if len(msg) < ulen:
+            return
+        uname = msg.get(ulen)
+
+        # Parse PLEN
+        if len(msg) < 1:
+            return
+        plen = msg.get_uint8()
+        if len(msg) < plen:
+            return
+        if plen == 0:
+            log.msg("Password length is 0")
+            self.sendRfc1929Reply(False)
+            return
+        passwd = msg.get(plen)
+
+        # Ensure there is no trailing garbage
+        if len(msg) > 0:
+            log.msg("Peer sent trailing garbage after RFC1929 auth")
+            self.transport.loseConnection()
+            return
+        self.buf.clear()
+
+        if not self.processRfc1929Auth(str(uname), str(passwd)):
+            self.sendRfc1929Reply(False)
+        else:
+            self.sendRfc1929Reply(True)
+
+    def processRfc1929Auth(self, uname, passwd):
+        """
+        Handle the RFC1929 Username/Password received from the client
+        """
+
+        return False
+
+    def sendRfc1929Reply(self, success):
+        """
+        Send a RFC1929 Username/Password Authentication response
+        """
+
+        msg = _ByteBuffer()
+        msg.add_uint8(_SOCKS_RFC1929_VER)
+        if success:
+            msg.add_uint8(_SOCKS_RFC1929_SUCCESS)
+            self.transport.write(str(msg))
+            self.state = self.ST_READ_REQUEST
+        else:
+            msg.add_uint8(_SOCKS_RFC1929_FAIL)
+            self.transport.write(str(msg))
+            self.transport.loseConnection()
+
+    def processNoAuthRequired(self):
+        """
+        Handle the RFC1928 No Authentication Required
+        """
+
+        self.state = self.ST_READ_REQUEST
+        self.processRequest()
+
+    def processRequest(self):
+        """
+        Parse the client request, and setup the TCP/IP connection
+        """
+
+        msg = self.buf.peek()
+        if len(msg) < 4:
+            return
+
+        # Parse VER, CMD, RSV, ATYP
+        ver = msg.get_uint8()
+        cmd = msg.get_uint8()
+        rsv = msg.get_uint8()
+        atyp = msg.get_uint8()
+        if ver != _SOCKS_VERSION:
+            log.msg("Invalid SOCKS version: '%d'" % ver)
+            self.sendReply(SOCKSv5Reply.GeneralFailure)
+            return
+        if cmd not in self.ACCEPTABLE_CMDS:
+            log.msg("Invalid SOCKS command: '%d'" % cmd)
+            self.sendReply(SOCKSv5Reply.CommandNotSupported)
+            return
+        if rsv != _SOCKS_RSV:
+            log.msg("Invalid SOCKS RSV: '%d'" % rsv)
+            self.sendReply(SOCKSv5Reply.GeneralFailure)
+            return
+
+        # Deal with the address
+        addr = None
+        if atyp == _SOCKS_ATYP_IP_V4:
+            if len(msg) < 4:
+                return
+            addr = socket.inet_ntoa(str(msg.get(4)))
+        elif atyp == _SOCKS_ATYP_IP_V6:
+            if len(msg) < 16:
+                return
+            try:
+                addr = socket.inet_ntop(socket.AF_INET6, str(msg.get(16)))
+            except:
+                log.msg("Failed to parse IPv6 address")
+                self.sendReply(SOCKSv5Reply.AddressTypeNotSupported)
+                return
+        elif atyp == _SOCKS_ATYP_DOMAINNAME:
+            if len(msg) < 1:
+                return
+            alen = msg.get_uint8()
+            if alen == 0:
+                log.msg("Domain name length is 0")
+                self.sendReply(SOCKSv5Reply.GeneralFailure)
+                return
+            if len(msg) < alen:
+                return
+            addr = str(msg.get(alen))
+        else:
+            log.msg("Invalid SOCKS address type: '%d'" % atyp)
+            self.sendReply(SOCKSv5Reply.AddressTypeNotSupported)
+            return
+
+        # Deal with the port
+        if len(msg) < 2:
+            return
+        port = msg.get_uint16(True)
+
+        # Ensure there is no trailing garbage
+        if len(msg) > 0:
+            log.msg("Peer sent trailing garbage after request")
+            self.transport.loseConnection()
+            return
+        self.buf.clear()
+
+        if cmd == _SOCKS_CMD_CONNECT:
+            self.processCmdConnect(addr, port)
+        elif cmd == _SOCKS_CMD_BIND:
+            self.processCmdBind(addr, port)
+        elif cmd == _SOCKS_CMD_UDP_ASSOCIATE:
+            self.processCmdUdpAssociate(addr, port)
+        else:
+            # Should *NEVER* happen
+            log.msg("Unimplemented command received")
+            self.transport.loseConnection()
+
+    def processCmdConnect(self, addr, port):
+        """
+        Open a TCP/IP connection to the peer
+        """
+
+        d = self.connectClass(addr, port, SOCKSv5Outgoing, self)
+        d.addErrback(self.handleCmdConnectFailure)
+        self.state = self.ST_CONNECTING
+
+    def connectClass(self, addr, port, klass, *args):
+        return protocol.ClientCreator(reactor, klass, *args).connectTCP(addr, port)
+
+    def handleCmdConnectFailure(self, failure):
+        log.err(failure, "Failed to connect to peer")
+
+        # Map common twisted errors to SOCKS error codes
+        if failure.type == error.NoRouteError:
+            self.sendReply(SOCKSv5Reply.NetworkUnreachable)
+        elif failure.type == error.ConnectionRefusedError:
+            self.sendReply(SOCKSv5Reply.ConnectionRefused)
+        elif failure.type == error.TCPTimedOutError or failure.type == error.TimeoutError:
+            self.sendReply(SOCKSv5Reply.TTLExpired)
+        elif failure.type == error.UnsupportedAddressFamily:
+            self.sendReply(SOCKSv5Reply.AddressTypeNotSupported)
+        else:
+            self.sendReply(SOCKSv5Reply.GeneralFailure)
+
+    def processCmdBind(self, addr, port):
+        self.sendReply(SOCKSv5Reply.CommandNotSupported)
+
+    def processCmdUdpAssociate(self, addr, port):
+        self.sendReply(SOCKSv5Reply.CommandNotSupported)
+
+    def sendReply(self, reply):
+        """
+        Send a reply to the request, and complete circuit setup
+        """
+
+        msg = _ByteBuffer()
+        msg.add_uint8(_SOCKS_VERSION)
+        msg.add_uint8(reply)
+        msg.add_uint8(_SOCKS_RSV)
+        if reply == SOCKSv5Reply.Succeeded:
+            host = self.transport.getHost()
+            port = host.port
+            try:
+                raw_addr = socket.inet_aton(host.host)
+                msg.add_uint8(_SOCKS_ATYP_IP_V4)
+                msg.add(raw_addr)
+            except socket.error:
+                try:
+                    raw_addr = socket.inet_pton(socket.AF_INET6, host.host)
+                    msg.add_uint8(_SOCKS_ATYP_IP_V6)
+                    msg.add(raw_addr)
+                except:
+                    log.msg("Failed to parse bound address")
+                    self.sendReply(SOCKSv5Reply.GeneralFailure)
+                    return
+            msg.add_uint16(port, True)
+            self.transport.write(str(msg))
+
+            self.state = self.ST_ESTABLISHED
+        else:
+            msg.add_uint8(_SOCKS_ATYP_IP_V4)
+            msg.add_uint32(0, True)
+            msg.add_uint16(0, True)
+            self.transport.write(str(msg))
+            self.transport.loseConnection()
+
+    def log(self, proto, data):
+        pass
+
+
+class SOCKSv5Factory(protocol.Factory):
+    """
+    A SOCKSv5 Factory.
+    """
+
+    def __init__(self, log):
+        self.logging = log
+
+    def buildProtocol(self, addr):
+        return SOCKSv5Protocol(self.logging, reactor)
+
+
+class _ByteBuffer(bytearray):
+    """
+    A byte buffer, based on bytearray.  get_* always removes reads from the
+    head (and is destructive), and add_* appends to the tail.
+    """
+
+    def add_uint8(self, val):
+        self.extend(struct.pack("B", val))
+
+    def get_uint8(self):
+        return self.pop(0)
+
+    def add_uint16(self, val, htons=False):
+        if htons:
+            self.extend(struct.pack("!H", val))
+        else:
+            self.extend(struct.pack("H", val))
+
+    def get_uint16(self, ntohs=False):
+        if ntohs:
+            ret = struct.unpack("!H", self[0:2])[0]
+        else:
+            ret = struct.unpack("H", self[0:2])[0]
+        del self[0:2]
+        return ret
+
+    def add_uint32(self, val, htonl=False):
+        if htonl:
+            self.extend(struct.pack("!I", val))
+        else:
+            self.extend(struct.pack("I", val))
+
+    def add(self, val):
+        self.extend(val)
+
+    def get(self, len):
+        ret = self[0:len]
+        del self[0:len]
+        return ret
+
+    def peek(self):
+        ret = _ByteBuffer()
+        ret[:] = self
+        return ret
+
+    def clear(self):
+        del self[0:]
+
+    def __repr__(self):
+        return self.decode('ISO-8859-1')

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-privacy/packages/obfsproxy.git



More information about the Pkg-privacy-commits mailing list