[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