[Pkg-privacy-commits] [obfsproxy] 289/353: Add support for connecting via a HTTPS CONNECT proxy.

Ximin Luo infinity0 at moszumanska.debian.org
Sat Aug 22 13:02:12 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 2477925050651940a40741b2c12910bb36250594
Author: Yawning Angel <yawning at torproject.org>
Date:   Tue Apr 15 10:00:21 2014 +0000

    Add support for connecting via a HTTPS CONNECT proxy.
    
    This adds a HTTPS CONNECT client.  It's been lightly tested with both
    privoxy and apache2 and appears to work, both without authentication
    and with Basic.
---
 ChangeLog                    |   2 +
 obfsproxy/managed/client.py  |   5 --
 obfsproxy/network/http.py    | 136 +++++++++++++++++++++++++++++++++++++++++++
 obfsproxy/network/network.py |  19 ++++--
 obfsproxy/network/socks.py   |   6 ++
 obfsproxy/network/socks5.py  |   7 ++-
 obfsproxy/pyobfsproxy.py     |   5 --
 7 files changed, 162 insertions(+), 18 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 9fa0221..77e8062 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,6 @@
 Changes in version 0.2.8 - UNRELEASED
+ - Support connecting over a HTTPS CONNECT proxy.  Patch by Yawning Angel.
+   Fixes #11409.
  - Support connecting over SOCKS4(a) and SOCKS5.  Based on the patch by
    Arturo Filastò with changes by Yawning Angel.  Fixes #8956.
 
diff --git a/obfsproxy/managed/client.py b/obfsproxy/managed/client.py
index 1df6baf..1a49074 100644
--- a/obfsproxy/managed/client.py
+++ b/obfsproxy/managed/client.py
@@ -32,11 +32,6 @@ def do_managed_client():
     # Apply the proxy settings if any
     proxy = ptclient.config.getProxy()
     if proxy:
-        # XXX temporarily: till we implement HTTP
-        if proxy.scheme == "http":
-            log.error("HTTP CONNECT proxy not supported yet")
-            ptclient.reportProxyError("HTTP CONNECT not supported yet")
-            return
         ptclient.reportProxySuccess()
 
     for transport in ptclient.getTransports():
diff --git a/obfsproxy/network/http.py b/obfsproxy/network/http.py
new file mode 100644
index 0000000..9aa62d1
--- /dev/null
+++ b/obfsproxy/network/http.py
@@ -0,0 +1,136 @@
+from base64 import b64encode
+from twisted.internet.interfaces import IStreamClientEndpoint
+from twisted.internet.protocol import ClientFactory
+from twisted.internet.defer import Deferred
+from twisted.web.http import HTTPClient
+from zope.interface import implementer
+
+import obfsproxy.common.log as logging
+
+"""
+HTTP CONNECT Client:
+
+Next up on the list of things one would expect Twisted to provide, but does not
+is an endpoint for outgoing connections through a HTTP CONNECT proxy.
+
+Limitations:
+ * Only Basic Authentication is supported (RFC2617).
+"""
+
+log = logging.get_obfslogger()
+
+# Create the body of the RFC2617 Basic Authentication 'Authorization' header.
+def _makeBasicAuth(username, password):
+    if username and password:
+        return "Basic " + b64encode(username + ':' + password)
+    elif username or password:
+        raise ValueError("expecting both a username *and* password")
+    else:
+        return None
+
+class HTTPConnectClient(HTTPClient):
+    deferred = None
+    host = None
+    port = None
+    proxy_addr = None
+    auth = None
+    instance_factory = None
+    instance = None
+
+    def __init__(self, deferred, host, port, proxy_addr, auth, instance_factory):
+        self.deferred = deferred
+        self.host = host
+        self.port = port
+        self.proxy_addr = proxy_addr
+        self.auth = auth
+        self.instance_factory = instance_factory
+
+    def connectionMade(self):
+        log.debug("HTTPConnectClient: Proxy connection established: %s:%d" % (log.safe_addr_str(self.proxy_addr.host), self.proxy_addr.port))
+
+        self.sendCommand("CONNECT", "%s:%d" % (self.host, self.port))
+        if self.auth:
+            self.sendHeader("Proxy-Authorization", self.auth)
+        self.endHeaders()
+
+    def connectionLost(self, reason):
+        if self.instance:
+            self.instance.connectionLost(reason)
+        else:
+            self.onConnectionError(reason)
+
+    def handleEndHeaders(self):
+        log.info("HTTPConnectClient: Connected to %s:%d via %s:%d" % (log.safe_addr_str(self.host), self.port, log.safe_addr_str(self.proxy_addr.host), self.proxy_addr.port))
+
+        self.setRawMode()
+        self.instance = self.instance_factory.buildProtocol(self.proxy_addr)
+        self.instance.makeConnection(self.transport)
+        self.deferred.callback(self.instance)
+
+        tmp = self.clearLineBuffer()
+        if tmp:
+            self.instance.dataReceived(tmp)
+
+    def handleStatus(self, version, status, message):
+        if status != "200":
+            self.onConnectionError(IOError("HTTPConnectClient: Proxy returned status: %s" % status))
+
+    def rawDataReceived(self, data):
+        log.debug("HTTPConnectClient: Received %d bytes of proxied data" % len(data))
+        if self.instance:
+            self.instance.dataReceived(data)
+        else:
+            raise RuntimeError("HTTPConnectClient.rawDataReceived() called with no instance")
+
+    def onConnectionError(self, reason):
+        if self.deferred:
+            log.warning("HTTPConnectClient: Connect error: %s" % reason)
+            self.deferred.errback(reason)
+            self.deferred = None
+            self.transport.loseConnection()
+
+class HTTPConnectClientFactory(ClientFactory):
+    deferred = None
+    host = None
+    port = None
+    auth = None
+    instance_factory = None
+
+    def __init__(self, host, port, auth, instance_factory):
+        self.deferred = Deferred()
+        self.host = host
+        self.port = port
+        self.auth = auth
+        self.instance_factory = instance_factory
+
+    def buildProtocol(self, addr):
+        proto = HTTPConnectClient(self.deferred, self.host, self.port, addr, self.auth, self.instance_factory)
+        return proto
+
+    def startedConnecting(self, connector):
+        self.instance_factory.startedConnectiong(connector)
+
+    def clientConnectionFailed(self, connector, reason):
+        self.instance_factory.clientConnectionFailed(connector, reason)
+
+    def clientConnectionLost(self, connector, reason):
+        self.instance_factory.clientConnectionLost(connector, reason)
+
+ at implementer(IStreamClientEndpoint)
+class HTTPConnectClientEndpoint(object):
+    host = None
+    port = None
+    endpoint = None
+    auth = None
+
+    def __init__(self, host, port, endpoint, username=None, password=None):
+        self.host = host
+        self.port = port
+        self.endpoint = endpoint
+        self.auth = _makeBasicAuth(username, password)
+
+    def connect(self, instance_factory):
+        f = HTTPConnectClientFactory(self.host, self.port, self.auth, instance_factory)
+        d = self.endpoint.connect(f)
+        d.addCallback(lambda proto: f.deferred)
+        return d
diff --git a/obfsproxy/network/network.py b/obfsproxy/network/network.py
index a6c135e..2ecc08d 100644
--- a/obfsproxy/network/network.py
+++ b/obfsproxy/network/network.py
@@ -10,6 +10,8 @@ import obfsproxy.common.heartbeat as heartbeat
 import obfsproxy.network.buffer as obfs_buf
 import obfsproxy.transports.base as base
 
+from obfsproxy.network.http import HTTPConnectClientEndpoint
+
 log = logging.get_obfslogger()
 
 """
@@ -396,10 +398,11 @@ def create_proxy_client(host, port, proxy_spec, instance):
 
     log.debug("Connecting via %s proxy %s:%d" % (proxy_spec.scheme, log.safe_addr_str(proxy_spec.hostname), proxy_spec.port))
 
+    TCPPoint = HostnameEndpoint(reactor, proxy_spec.hostname, proxy_spec.port)
+    username = proxy_spec.username
+    password = proxy_spec.password
+
     if proxy_spec.scheme in ["socks4a", "socks5"]:
-        TCPPoint = HostnameEndpoint(reactor, proxy_spec.hostname, proxy_spec.port)
-        username = proxy_spec.username
-        password = proxy_spec.password
         if proxy_spec.scheme == "socks4a":
             if username:
                 assert(password == None)
@@ -416,8 +419,14 @@ def create_proxy_client(host, port, proxy_spec, instance):
         d = SOCKSPoint.connect(instance)
         return d
     elif proxy_spec.scheme == "http":
-        # TODO: This should be supported one day
-        raise NotImplementedError("HTTP CONNECT proxy unsupported")
+        if username and password:
+            HTTPPoint = HTTPConnectClientEndpoint(host, port, TCPPoint,
+                                                  username, password)
+        else:
+            assert(username == None and password == None)
+            HTTPPoint = HTTPConnectClientEndpoint(host, port, TCPPoint)
+        d = HTTPPoint.connect(instance)
+        return d
     else:
         # Should *NEVER* happen
         raise RuntimeError("Invalid proxy scheme %s" % proxy_spec.scheme)
diff --git a/obfsproxy/network/socks.py b/obfsproxy/network/socks.py
index 268c1e8..dc09f91 100644
--- a/obfsproxy/network/socks.py
+++ b/obfsproxy/network/socks.py
@@ -76,6 +76,12 @@ class OBFSSOCKSv5OutgoingFactory(protocol.Factory):
     def buildProtocol(self, addr):
         return OBFSSOCKSv5Outgoing(self.socks)
 
+    def clientConnectionFailed(self, connector, reason):
+        self.socks.transport.loseConnection()
+
+    def clientConnectionLost(self, connector, reason):
+        self.socks.transport.loseConnection()
+
 class OBFSSOCKSv5Protocol(socks5.SOCKSv5Protocol, network.GenericProtocol):
     """
     Represents an upstream connection from a SOCKS client to our SOCKS
diff --git a/obfsproxy/network/socks5.py b/obfsproxy/network/socks5.py
index a02759a..5cc62a2 100644
--- a/obfsproxy/network/socks5.py
+++ b/obfsproxy/network/socks5.py
@@ -408,9 +408,6 @@ class SOCKSv5Protocol(protocol.Protocol):
 
     def handleCmdConnectFailure(self, failure):
         log.error("CMD CONNECT: %s" % failure.getErrorMessage())
-        failure.trap(error.NoRouteError, error.ConnectionRefusedError,
-                     error.TCPTimedOutError, error.TimeoutError,
-                     error.UnsupportedAddressFamily)
 
         # Map common twisted errors to SOCKS error codes
         if failure.type == error.NoRouteError:
@@ -424,6 +421,10 @@ class SOCKSv5Protocol(protocol.Protocol):
         else:
             self.sendReply(SOCKSv5Reply.GeneralFailure)
 
+        failure.trap(error.NoRouteError, error.ConnectionRefusedError,
+                     error.TCPTimedOutError, error.TimeoutError,
+                     error.UnsupportedAddressFamily)
+
     def processCmdBind(self, addr, port):
         self.sendReply(SOCKSv5Reply.CommandNotSupported)
 
diff --git a/obfsproxy/pyobfsproxy.py b/obfsproxy/pyobfsproxy.py
index 6a31646..82a4b54 100755
--- a/obfsproxy/pyobfsproxy.py
+++ b/obfsproxy/pyobfsproxy.py
@@ -133,11 +133,6 @@ def consider_cli_args(args):
             log.error("Failed to parse proxy specifier: %s" % e)
             sys.exit(1)
 
-        # XXX temporarily: till we implement HTTP
-        if proxy.scheme == 'http':
-            log.error("obfsproxy does not yet support HTTP CONNECT")
-            sys.exit(1)
-
 def run_transport_setup(pt_config):
     """Run the setup() method for our transports."""
     for transport, transport_class in transports.transports.items():

-- 
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