[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