[Pkg-privacy-commits] [pyptlib] 127/136: Add support for TOR_PT_PROXY

Ximin Luo infinity0 at moszumanska.debian.org
Sat Aug 22 13:25:19 UTC 2015


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

infinity0 pushed a commit to branch master
in repository pyptlib.

commit 80142d55f135b02cd42753370f683385055a63e3
Author: Yawning Angel <yawning at schwanenlied.me>
Date:   Wed Mar 19 06:57:37 2014 +0000

    Add support for TOR_PT_PROXY
    
    A from the spec implementation of TOR_PT_PROXY and the related APIs.
    
    Added:
     * ClientTransportPlugin.reportProxySuccess(self) - Report proxy
       config success.
     * ClientTransportPlugin.reportProxyError(self, msg) - Report proxy
       config error.
     * ClientConfig.getProxy(self) - Obtain the configured proxy as a
       urlparse.urlsplit tuple (urlparse.SplitResult).  This will return
       None if no proxy is configured.
    
    After calling urlsplit the code will correctly validate that it is in a
    form specified by checking:
     * scheme is one of http, socks4a, socks5.
     * hostname is a valid IP address (No resolution is done).
     * port is a valid port.
     * No path, query, or fragment is present.
     * Only the username is specified for socks4a.
    
    See also:
     * https://trac.torproject.org/projects/tor/ticket/8402
     * https://gitweb.torproject.org/torspec.git/blob/HEAD:/proposals/ideas/xxx-pluggable-transports-through-proxy.txt
---
 ChangeLog                   |   5 ++
 pyptlib/client.py           |  31 ++++++++++++
 pyptlib/client_config.py    |  70 ++++++++++++++++++++++++++-
 pyptlib/config.py           |  13 +++++
 pyptlib/core.py             |   5 +-
 pyptlib/test/test_client.py | 112 +++++++++++++++++++++++++++++++++++++++++++-
 6 files changed, 233 insertions(+), 3 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 6893231..1614ad0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+Changes in version 0.0.6 - UNRELEASED
+ - Add support for TOR_PT_PROXY to allow client side proxy use.  Patch by
+   Yawning Angel.
+
+
 Changes in version 0.0.5 - 2013-09-30
  - API change to expose server-side transport options. Fixes #8979.
 
diff --git a/pyptlib/client.py b/pyptlib/client.py
index b4238a7..ed769cd 100644
--- a/pyptlib/client.py
+++ b/pyptlib/client.py
@@ -15,6 +15,7 @@ class ClientTransportPlugin(TransportPlugin):
     """
     configType = ClientConfig
     methodName = 'CMETHOD'
+    reportedProxy = False
 
     def reportMethodSuccess(self, name, protocol, addrport, args=None, optArgs=None):
         """
@@ -36,6 +37,36 @@ class ClientTransportPlugin(TransportPlugin):
             methodLine = methodLine + ' OPT-ARGS=' + args.join(',')
         self.emit(methodLine)
 
+    def reportProxySuccess(self):
+        """
+        Write a message to stdout announcing that the specified proxy will be
+        used.
+        """
+
+        if not self.config.proxy:
+            raise RuntimeError("reportProxySuccess() when no proxy specified")
+        elif self.reportedProxy:
+            raise RuntimeError("reportProxySuccess() after status already reported")
+        else:
+            self.reportedProxy = True
+            self.emit("PROXY true")
+
+    def reportProxyError(self, msg=None):
+        """
+        Write a message to stdout announcing that the specified proxy can not be
+        used.
+        """
+
+        if not self.config.proxy:
+            raise RuntimeError("reportProxyError() when no proxy specified")
+        elif self.reportedProxy:
+            raise RuntimeError("reportProxyError() after status already reported")
+        else:
+            self.reportedProxy = True
+            proxyLine = 'PROXY-ERROR'
+            if msg and len(msg) > 0:
+                proxyLine += ' ' + msg
+            self.emit(proxyLine)
 
 def init(supported_transports):
     """DEPRECATED. Use ClientTransportPlugin().init() instead."""
diff --git a/pyptlib/client_config.py b/pyptlib/client_config.py
index ced4dcd..b7393fc 100644
--- a/pyptlib/client_config.py
+++ b/pyptlib/client_config.py
@@ -5,11 +5,17 @@
 Low-level parts of pyptlib that are only useful to clients.
 """
 
-from pyptlib.config import Config, get_env
+from pyptlib.config import Config, ProxyError, get_env
+from pyptlib import util
+from urlparse import urlsplit
+
+SUPPORTED_PROXY_SCHEMES = ['http', 'socks4a', 'socks5']
 
 class ClientConfig(Config):
     """
     A client-side pyptlib configuration.
+
+    :var urlparse.SplitResult proxy: The proxy that should be used for outgoing connections.  None if no proxy is required.
     """
 
     @classmethod
@@ -18,9 +24,71 @@ class ClientConfig(Config):
         Build a ClientConfig from environment variables.
 
         :raises: :class:`pyptlib.config.EnvError` if environment was incomplete or corrupted.
+        :raises: :class:`pyptlib.config.ProxyError` if proxy was incomplete or corrupted.
         """
+
+        # TOR_PT_PROXY is either totally missing or is a valid URI specifying
+        # the proxy that pluggable transports should use.
+        def missing_or_valid_proxy_uri(k, v):
+            if v is None: return v
+            return parseProxyURI(v)
+
         return cls(
             stateLocation = get_env('TOR_PT_STATE_LOCATION'),
             managedTransportVer = get_env('TOR_PT_MANAGED_TRANSPORT_VER').split(','),
             transports = get_env('TOR_PT_CLIENT_TRANSPORTS').split(','),
+            proxy = get_env('TOR_PT_PROXY', missing_or_valid_proxy_uri)
             )
+
+    def __init__(self,
+                 stateLocation,
+                 managedTransportVer=None,
+                 transports=None,
+                 proxy=None):
+        Config.__init__(self, stateLocation, managedTransportVer, transports)
+        self.proxy = proxy
+
+    def getProxy(self):
+        """
+        :returns: :attr:`pyptlib.client_config.ClientConfig.proxy`
+        """
+        return self.proxy
+
+def parseProxyURI(uri_str):
+    try:
+        uri = urlsplit(uri_str, allow_fragments=False)
+    except Exception, e:
+        raise ProxyError("Error parsing proxy URI (%s)" % uri_str)
+
+    if not uri.scheme in SUPPORTED_PROXY_SCHEMES:
+        raise ProxyError("Invalid scheme (%s)" % uri.scheme)
+    if uri.scheme == 'socks4a' and uri.password:
+        raise ProxyError("Proxy URI specified SOCKS4a and a password")
+    elif uri.scheme == 'socks5':
+        if uri.username and not uri.password:
+            raise ProxyError("Proxy URI specified SOCKS5, a username and no password")
+        if uri.password and not uri.username:
+            raise ProxyError("Proxy URI specified SOCKS5, a password and no username")
+        if uri.username and len(uri.username) > 255:
+            raise ProxyError("Proxy URI specified an oversized username")
+        if uri.password and len(uri.password) > 255:
+            raise ProxyError("Proxy URI specified an oversized password")
+    if uri.netloc == '':
+        raise ProxyError("Proxy URI is missing a netloc (%s)" % uri.netloc)
+    if not uri.hostname:
+        raise ProxyError("Proxy URI is missing a hostname")
+    if not uri.port:
+        raise ProxyError("Proxy URI is missing a port")
+    if uri.path != '':
+        raise ProxyError("Proxy URI has a path when none expected (%s)" %
+                         uri.path)
+    if uri.query != '':
+        raise ProxyError("Proxy URI has query when none expected (%s)" %
+                         uri.query)
+    try:
+        addr_str = uri.hostname + ":" + str(uri.port)
+        host, port = util.parse_addr_spec(addr_str)
+    except:
+        raise ProxyError("Proxy URI has invalid netloc (%s)" %
+                         uri.netloc)
+    return uri
diff --git a/pyptlib/config.py b/pyptlib/config.py
index 77c826e..cf8d02a 100644
--- a/pyptlib/config.py
+++ b/pyptlib/config.py
@@ -81,9 +81,22 @@ def get_env(key, validate=env_has_k):
     """
     try:
         return validate(key, os.getenv(key))
+    except ProxyError:
+        raise
     except Exception, e:
         raise EnvError("error parsing env-var: %s: %s" % (key, e), e)
 
+class ProxyError(Exception):
+    """
+    Thrown when the proxy specifier is incomplete or corrupted.
+    """
+    def __init__(self, message=None, cause=None):
+        self.message = message
+        self.cause = cause
+
+    def __str__(self):
+        return self.message or self.cause.message
+
 class EnvError(Exception):
     """
     Thrown when the environment is incomplete or corrupted.
diff --git a/pyptlib/core.py b/pyptlib/core.py
index eba0cd5..7d57b7b 100644
--- a/pyptlib/core.py
+++ b/pyptlib/core.py
@@ -1,6 +1,6 @@
 import sys
 
-from pyptlib.config import EnvError, SUPPORTED_TRANSPORT_VERSIONS
+from pyptlib.config import EnvError, ProxyError, SUPPORTED_TRANSPORT_VERSIONS
 
 
 class TransportPlugin(object):
@@ -56,6 +56,9 @@ class TransportPlugin(object):
         """
         try:
             return self.configType.fromEnv()
+        except ProxyError, e:
+            self.emit('PROXY-ERROR %s' % str(e))
+            raise EnvError(str(e))
         except EnvError, e:
             self.emit('ENV-ERROR %s' % str(e))
             raise e
diff --git a/pyptlib/test/test_client.py b/pyptlib/test/test_client.py
index cac5772..cca287a 100644
--- a/pyptlib/test/test_client.py
+++ b/pyptlib/test/test_client.py
@@ -2,7 +2,7 @@ import os
 import unittest
 
 from pyptlib.client import ClientTransportPlugin
-from pyptlib.config import EnvError, Config
+from pyptlib.config import EnvError, ProxyError, Config
 from pyptlib.test.test_core import PluginCoreTestMixin
 
 class testClient(PluginCoreTestMixin, unittest.TestCase):
@@ -36,6 +36,116 @@ class testClient(PluginCoreTestMixin, unittest.TestCase):
         self.assertRaises(EnvError, self.plugin._loadConfigFromEnv)
         self.assertOutputLinesStartWith("ENV-ERROR ")
 
+    def test_fromEnv_proxy_http_legit(self):
+        """Legit enviornment, with http proxy"""
+        TEST_ENVIRON = { "TOR_PT_STATE_LOCATION" : "/pt_stat",
+                         "TOR_PT_MANAGED_TRANSPORT_VER" : "1",
+                         "TOR_PT_CLIENT_TRANSPORTS" : "dummy",
+                         "TOR_PT_PROXY" : "http://user:password@192.0.2.1:80" }
+
+        os.environ = TEST_ENVIRON
+        self.plugin.config = self.plugin._loadConfigFromEnv()
+        self.assertOutputLinesEmpty()
+        proxy = self.plugin.config.getProxy()
+        self.assertEquals(proxy.geturl(), TEST_ENVIRON["TOR_PT_PROXY"])
+        self.assertEquals(proxy.scheme, "http")
+        self.assertEquals(proxy.hostname, "192.0.2.1")
+        self.assertEquals(proxy.port, 80)
+        self.assertEquals(proxy.path, "")
+        self.assertEquals(proxy.query, "")
+        self.assertEquals(proxy.fragment, "")
+        self.assertEquals(proxy.username, "user")
+        self.assertEquals(proxy.password, "password")
+
+    def test_fromEnv_proxy_socks4a_legit(self):
+        TEST_ENVIRON = { "TOR_PT_STATE_LOCATION" : "/pt_stat",
+                         "TOR_PT_MANAGED_TRANSPORT_VER" : "1",
+                         "TOR_PT_CLIENT_TRANSPORTS" : "dummy",
+                         "TOR_PT_PROXY" : "socks4a://user@192.0.2.1:1080" }
+
+        os.environ = TEST_ENVIRON
+        self.plugin.config = self.plugin._loadConfigFromEnv()
+        self.assertOutputLinesEmpty()
+        proxy = self.plugin.config.getProxy()
+        self.assertEquals(proxy.geturl(), TEST_ENVIRON['TOR_PT_PROXY'])
+        self.assertEquals(proxy.scheme, "socks4a")
+        self.assertEquals(proxy.hostname, "192.0.2.1")
+        self.assertEquals(proxy.port, 1080)
+        self.assertEquals(proxy.path, "")
+        self.assertEquals(proxy.query, "")
+        self.assertEquals(proxy.fragment, "")
+        self.assertEquals(proxy.username, "user")
+        self.assertEquals(proxy.password, None)
+
+    def test_fromEnv_proxy_socks5_legit(self):
+        TEST_ENVIRON = { "TOR_PT_STATE_LOCATION" : "/pt_stat",
+                         "TOR_PT_MANAGED_TRANSPORT_VER" : "1",
+                         "TOR_PT_CLIENT_TRANSPORTS" : "dummy",
+                         "TOR_PT_PROXY" : "socks5://user:password at 192.0.2.1:1080" }
+
+        os.environ = TEST_ENVIRON
+        self.plugin.config = self.plugin._loadConfigFromEnv()
+        self.assertOutputLinesEmpty()
+        proxy = self.plugin.config.getProxy()
+        self.assertEquals(proxy.geturl(), TEST_ENVIRON['TOR_PT_PROXY'])
+        self.assertEquals(proxy.scheme, "socks5")
+        self.assertEquals(proxy.hostname, "192.0.2.1")
+        self.assertEquals(proxy.port, 1080)
+        self.assertEquals(proxy.path, "")
+        self.assertEquals(proxy.query, "")
+        self.assertEquals(proxy.fragment, "")
+        self.assertEquals(proxy.username, "user")
+        self.assertEquals(proxy.password, "password")
+
+    def test_fromEnv_proxy_invalid_scheme(self):
+        TEST_ENVIRON = { "TOR_PT_STATE_LOCATION" : "/pt_stat",
+                         "TOR_PT_MANAGED_TRANSPORT_VER" : "1",
+                         "TOR_PT_CLIENT_TRANSPORTS" : "dummy",
+                         "TOR_PT_PROXY" : "gopher://user:password@192.0.2.1:70" }
+
+        os.environ = TEST_ENVIRON
+        self.assertRaises(EnvError, self.plugin._loadConfigFromEnv)
+        self.assertOutputLinesStartWith("PROXY-ERROR ")
+
+    def test_fromEnv_proxy_missing_scheme(self):
+        TEST_ENVIRON = { "TOR_PT_STATE_LOCATION" : "/pt_stat",
+                         "TOR_PT_MANAGED_TRANSPORT_VER" : "1",
+                         "TOR_PT_CLIENT_TRANSPORTS" : "dummy",
+                         "TOR_PT_PROXY" : "user:password at 192.0.2.1:70" }
+
+        os.environ = TEST_ENVIRON
+        self.assertRaises(EnvError, self.plugin._loadConfigFromEnv)
+        self.assertOutputLinesStartWith("PROXY-ERROR ")
+
+    def test_fromEnv_proxy_missing_port(self):
+        TEST_ENVIRON = { "TOR_PT_STATE_LOCATION" : "/pt_stat",
+                         "TOR_PT_MANAGED_TRANSPORT_VER" : "1",
+                         "TOR_PT_CLIENT_TRANSPORTS" : "dummy",
+                         "TOR_PT_PROXY" : "http://user:password@192.0.2.1" }
+
+        os.environ = TEST_ENVIRON
+        self.assertRaises(EnvError, self.plugin._loadConfigFromEnv)
+        self.assertOutputLinesStartWith("PROXY-ERROR ")
+
+    def test_fromEnv_proxy_invalid_has_path(self):
+        TEST_ENVIRON = { "TOR_PT_STATE_LOCATION" : "/pt_stat",
+                         "TOR_PT_MANAGED_TRANSPORT_VER" : "1",
+                         "TOR_PT_CLIENT_TRANSPORTS" : "dummy",
+                         "TOR_PT_PROXY" : "http://user:password@192.0.2.1/path/" }
+
+        os.environ = TEST_ENVIRON
+        self.assertRaises(EnvError, self.plugin._loadConfigFromEnv)
+        self.assertOutputLinesStartWith("PROXY-ERROR ")
+
+    def test_fromEnv_proxy_invalid_has_socks4_passwd(self):
+        TEST_ENVIRON = { "TOR_PT_STATE_LOCATION" : "/pt_stat",
+                         "TOR_PT_MANAGED_TRANSPORT_VER" : "1",
+                         "TOR_PT_CLIENT_TRANSPORTS" : "dummy",
+                         "TOR_PT_PROXY" : "socks4a://user:password@192.0.2.1:1080" }
+
+        os.environ = TEST_ENVIRON
+        self.assertRaises(EnvError, self.plugin._loadConfigFromEnv)
+        self.assertOutputLinesStartWith("PROXY-ERROR ")
 
 if __name__ == '__main__':
     unittest.main()

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



More information about the Pkg-privacy-commits mailing list