[Pkg-privacy-commits] [txtorcon] 17/49: Cleanups and more tests for "tor:" endpoint parser

Ximin Luo infinity0 at debian.org
Mon Oct 19 13:49:51 UTC 2015


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

infinity0 pushed a commit to branch master
in repository txtorcon.

commit 57bcbc41e44b1ce34f02675ebcdbfdd212984d21
Author: meejah <meejah at meejah.ca>
Date:   Thu Feb 12 21:49:01 2015 -0700

    Cleanups and more tests for "tor:" endpoint parser
    
    still a few FIXMEs before this is "done".
---
 docs/releases.rst                           |   6 ++
 requirements.txt                            |   1 +
 test/test_endpoints.py                      |  67 ++++++++++++++----
 twisted/plugins/txtorcon_endpoint_parser.py |   1 +
 txtorcon/endpoints.py                       | 103 +++++++++++++++++++---------
 txtorcon/torstate.py                        |   3 +-
 6 files changed, 137 insertions(+), 44 deletions(-)

diff --git a/docs/releases.rst b/docs/releases.rst
index d660359..c9925ce 100644
--- a/docs/releases.rst
+++ b/docs/releases.rst
@@ -14,6 +14,12 @@ unreleased
 
  * :class:`txtorcon.interface.IStreamAttacher` handling was missing ``None`` and ``DO_NOT_ATTACH`` cases if a Deferred was returned.
  * add ``.is_built`` Deferred to :class:`txtorcon.Circuit` that get `callback()`d when the circuit becomes BUILT
+ * `david415 <https://github.com/david415>`_ ported his ``tor:``
+   endpoint parser so now both client and server endpoints are
+   supported. This means **any** Twisted program using endpoints can
+   use Tor as a client. For example, to connect to txtorcon's Web site:
+   ``ep = clientFromString("tor:timaq4ygg2iegci7.onion:80")``.
+   (In the future, I'd like to automatically launch Tor if required, too).
 
 
 v0.13.0
diff --git a/requirements.txt b/requirements.txt
index 80a733f..58935e7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,3 +4,4 @@
 Twisted>=11.1.0
 ipaddr>=2.1.10
 zope.interface>=3.6.1
+txsocksx>=1.13.0
diff --git a/test/test_endpoints.py b/test/test_endpoints.py
index 45ced81..2a730c9 100644
--- a/test/test_endpoints.py
+++ b/test/test_endpoints.py
@@ -34,6 +34,7 @@ from txtorcon import IProgressProvider
 from txtorcon import TorOnionAddress
 from txtorcon.util import NoOpProtocolFactory
 from txtorcon.endpoints import get_global_tor                       # FIXME
+from txtorcon.endpoints import default_tcp4_endpoint_generator
 
 import util
 
@@ -562,9 +563,10 @@ class FakeTorSocksEndpoint(object):
 
 class TestTorClientEndpoint(unittest.TestCase):
 
-    def test_clientConnectionFailed(self):
+    def test_client_connection_failed(self):
         """
-        This test is equivalent to txsocksx's TestSOCKS4ClientEndpoint.test_clientConnectionFailed
+        This test is equivalent to txsocksx's
+        TestSOCKS4ClientEndpoint.test_clientConnectionFailed
         """
         def FailTorSocksEndpointGenerator(*args, **kw):
             kw['failure'] = connectionRefusedFailure
@@ -573,7 +575,48 @@ class TestTorClientEndpoint(unittest.TestCase):
         d = endpoint.connect(None)
         return self.assertFailure(d, ConnectionRefusedError)
 
-    def test_defaultFactory(self):
+    def test_client_connection_failed_user_password(self):
+        """
+        Same as above, but with a username/password.
+        """
+        def FailTorSocksEndpointGenerator(*args, **kw):
+            kw['failure'] = connectionRefusedFailure
+            return FakeTorSocksEndpoint(*args, **kw)
+        endpoint = TorClientEndpoint(
+            'invalid host', 0,
+            socks_username='billy', socks_password='s333cure',
+            proxyEndpointGenerator=FailTorSocksEndpointGenerator)
+        d = endpoint.connect(None)
+        return self.assertFailure(d, ConnectionRefusedError)
+
+    def test_default_generator(self):
+        # just ensuring the default generator doesn't blow updoesn't blow up
+        default_tcp4_endpoint_generator(None, 'foo.bar', 1234)
+
+    def test_no_host(self):
+        self.assertRaises(
+            ValueError,
+            TorClientEndpoint, None, None
+        )
+
+    def test_parser_basic(self):
+        ep = clientFromString(None, 'tor:host=timaq4ygg2iegci7.onion:port=80:socksPort=9050')
+
+        self.assertEqual(ep.host, 'timaq4ygg2iegci7.onion')
+        self.assertEqual(ep.port, 80)
+        self.assertEqual(ep.socks_port, 9050)
+
+    def test_parser_user_password(self):
+        epstring = 'tor:host=torproject.org:port=443' + \
+                   ':socksUsername=foo:socksPassword=bar'
+        ep = clientFromString(None, epstring)
+
+        self.assertEqual(ep.host, 'torproject.org')
+        self.assertEqual(ep.port, 443)
+        self.assertEqual(ep.socks_username, 'foo')
+        self.assertEqual(ep.socks_password, 'bar')
+
+    def test_default_factory(self):
         """
         This test is equivalent to txsocksx's TestSOCKS5ClientEndpoint.test_defaultFactory
         """
@@ -583,7 +626,7 @@ class TestTorClientEndpoint(unittest.TestCase):
         endpoint.connect(None)
         self.assertEqual(endpoint.torSocksEndpoint.transport.value(), '\x05\x01\x00')
 
-    def test_goodPortRetry(self):
+    def test_good_port_retry(self):
         """
         This tests that our Tor client endpoint retry logic works correctly.
         We create a proxy endpoint that fires a connectionRefusedFailure
@@ -600,11 +643,11 @@ class TestTorClientEndpoint(unittest.TestCase):
             endpoint.connect(None)
             self.assertEqual(endpoint.torSocksEndpoint.transport.value(), '\x05\x01\x00')
 
-    def test_badPortRetry(self):
+    def test_bad_port_retry(self):
         """
         This tests failure to connect to the ports on the "try" list.
         """
-        fail_ports    = [1984, 666]
+        fail_ports = [1984, 666]
         for port in fail_ports:
             def TorSocksEndpointGenerator(*args, **kw):
                 kw['acceptPort'] = port
@@ -614,20 +657,20 @@ class TestTorClientEndpoint(unittest.TestCase):
             d = endpoint.connect(None)
             return self.assertFailure(d, ConnectionRefusedError)
 
-    def test_goodNoGuessSocksPort(self):
+    def test_good_no_guess_socks_port(self):
         """
-        This tests that if a SOCKS port is specified,
-        we *only* attempt to connect to that SOCKS port.
+        This tests that if a SOCKS port is specified, we *only* attempt to
+        connect to that SOCKS port.
         """
         def TorSocksEndpointGenerator(*args, **kw):
             kw['acceptPort'] = 6669
             kw['failure']    = connectionRefusedFailure
             return FakeTorSocksEndpoint(*args, **kw)
-        endpoint = TorClientEndpoint('', 0, proxyEndpointGenerator=TorSocksEndpointGenerator, socksPort=6669)
+        endpoint = TorClientEndpoint('', 0, proxyEndpointGenerator=TorSocksEndpointGenerator, socks_port=6669)
         endpoint.connect(None)
         self.assertEqual(endpoint.torSocksEndpoint.transport.value(), '\x05\x01\x00')
 
-    def test_badNoGuessSocksPort(self):
+    def test_bad_no_guess_socks_port(self):
         """
         This tests that are connection fails if we try to connect to an unavailable
         specified SOCKS port... even if there is a valid SOCKS port listening on
@@ -637,6 +680,6 @@ class TestTorClientEndpoint(unittest.TestCase):
             kw['acceptPort'] = 9050
             kw['failure']    = connectionRefusedFailure
             return FakeTorSocksEndpoint(*args, **kw)
-        endpoint = TorClientEndpoint('', 0, proxyEndpointGenerator=TorSocksEndpointGenerator, socksPort=6669)
+        endpoint = TorClientEndpoint('', 0, proxyEndpointGenerator=TorSocksEndpointGenerator, socks_port=6669)
         d = endpoint.connect(None)
         self.assertFailure(d, ConnectionRefusedError)
diff --git a/twisted/plugins/txtorcon_endpoint_parser.py b/twisted/plugins/txtorcon_endpoint_parser.py
index ec320e0..cbad80e 100644
--- a/twisted/plugins/txtorcon_endpoint_parser.py
+++ b/twisted/plugins/txtorcon_endpoint_parser.py
@@ -1,2 +1,3 @@
 import txtorcon
 tcpHiddenServiceEndpointParser = txtorcon.TCPHiddenServiceEndpointParser()
+tcpTorClientEndpointParser = txtorcon.TorClientEndpointStringParser()
diff --git a/txtorcon/endpoints.py b/txtorcon/endpoints.py
index 2545961..583d608 100644
--- a/txtorcon/endpoints.py
+++ b/txtorcon/endpoints.py
@@ -581,45 +581,55 @@ class TCPHiddenServiceEndpointParser(object):
                                                    local_port=localPort,
                                                    control_port=controlPort)
 
-def DefaultTCP4EndpointGenerator(*args, **kw):
+
+def default_tcp4_endpoint_generator(*args, **kw):
     """
-    Default generator used to create client-side TCP4ClientEndpoint instances.
-    We do this to make the unit tests work...
+    Default generator used to create client-side TCP4ClientEndpoint
+    instances.  We do this to make the unit tests work...
     """
     return TCP4ClientEndpoint(*args, **kw)
 
+
 @implementer(IStreamClientEndpoint)
 class TorClientEndpoint(object):
-    """I am an endpoint class who attempts to establish a SOCKS5 connection
-    with the system tor process. Either the user must pass a SOCKS port into my
-    constructor OR I will attempt to guess the Tor SOCKS port by iterating over a list of ports
-    that tor is likely to be listening on.
+    """
+    I am an endpoint class who attempts to establish a SOCKS5
+    connection with the system tor process. Either the user must pass
+    a SOCKS port into my constructor OR I will attempt to guess the
+    Tor SOCKS port by iterating over a list of ports that tor is
+    likely to be listening on.
 
-    :param host: The hostname to connect to.
-    This of course can be a Tor Hidden Service onion address.
+    :param host:
+        The hostname to connect to. This of course can be a Tor Hidden
+        Service onion address.
 
     :param port: The tcp port or Tor Hidden Service port.
 
     :param proxyEndpointGenerator: This is used for unit tests.
 
-    :param socksPort: This optional argument lets the user specify which Tor SOCKS port should be used.
+    :param socksPort:
+       This optional argument lets the user specify which Tor SOCKS
+       port should be used.
     """
     socks_ports_to_try = [9050, 9150]
 
-    def __init__(self, host, port, proxyEndpointGenerator=DefaultTCP4EndpointGenerator, socksHostname=None, socksPort=None, socksUsername=None, socksPassword=None):
+    def __init__(self, host, port,
+                 socks_hostname=None, socks_port=None,
+                 socks_username=None, socks_password=None,
+                 proxyEndpointGenerator=default_tcp4_endpoint_generator):
         if host is None or port is None:
             raise ValueError('host and port must be specified')
 
         self.host = host
         self.port = port
         self.proxyEndpointGenerator = proxyEndpointGenerator
-        self.socksHostname = socksHostname
-        self.socksPort = socksPort
-        self.socksUsername = socksUsername
-        self.socksPassword = socksPassword
+        self.socks_hostname = socks_hostname
+        self.socks_port = socks_port
+        self.socks_username = socks_username
+        self.socks_password = socks_password
 
-        if self.socksPort is None:
-            self.socksPortIter = iter(self.socks_ports_to_try)
+        if self.socks_port is None:
+            self.socks_portIter = iter(self.socks_ports_to_try)
             self.socksGuessingEnabled = True
         else:
             self.socksGuessingEnabled = False
@@ -628,20 +638,31 @@ class TorClientEndpoint(object):
         self.protocolfactory = protocolfactory
 
         if self.socksGuessingEnabled:
-            self.socksPort = self.socksPortIter.next()
+            self.socks_port = self.socks_portIter.next()
 
         d = self._try_connect()
         return d
 
     def _try_connect(self):
-        self.torSocksEndpoint = self.proxyEndpointGenerator(reactor, self.socksHostname, self.socksPort)
-
-        if self.socksUsername is None or self.socksPassword is None:
-            socks5ClientEndpoint = SOCKS5ClientEndpoint(self.host, self.port, self.torSocksEndpoint)
+        self.torSocksEndpoint = self.proxyEndpointGenerator(
+            reactor,
+            self.socks_hostname,
+            self.socks_port
+        )
+
+        if self.socks_username is None or self.socks_password is None:
+            socks5ClientEndpoint = SOCKS5ClientEndpoint(
+                self.host,
+                self.port,
+                self.torSocksEndpoint
+            )
         else:
-            socks5ClientEndpoint = SOCKS5ClientEndpoint(self.host, self.port, self.torSocksEndpoint,
-                                                        methods={ 'login': (self.socksUsername, self.socksPassword) })
-
+            socks5ClientEndpoint = SOCKS5ClientEndpoint(
+                self.host,
+                self.port,
+                self.torSocksEndpoint,
+                methods=dict(login=(self.socks_username, self.socks_password))
+            )
 
         d = socks5ClientEndpoint.connect(self.protocolfactory)
         if self.socksGuessingEnabled:
@@ -651,7 +672,7 @@ class TorClientEndpoint(object):
     def _retry_socks_port(self, failure):
         failure.trap(error.ConnectError)
         try:
-            self.socksPort = self.socksPortIter.next()
+            self.socks_port = self.socks_portIter.next()
         except StopIteration:
             return failure
         d = self._try_connect()
@@ -673,13 +694,29 @@ class TorClientEndpointStringParser(object):
 
     ``tor:host=timaq4ygg2iegci7.onion:port=80``
 
-    If ``socksPort`` is specified, it means only use that port to attempt to
-    proxy through Tor. If unspecified then try some likely socksPorts
-    such as [9050, 9150].
+    You may also include a username + password. By default, Tor will
+    not put two streams that provided different authentication on the
+    same circuit.
+
+    ``tor:host=torproject.org:port=443:socksUsername=foo:socksPassword=bar``
+
+    If ``socksPort`` is specified, it means only use that port to
+    attempt to proxy through Tor. If unspecified then try some likely
+    socksPorts such as [9050, 9150].
+
+    NOTE that I'm using camelCase variable names in the endpoint
+    string to be consistent with the rest of Twisted's naming (and
+    their endpoint parsers).
+
+    XXX FIXME if there is no Tor instance found at socksPort, we
+    should launch one. Perhaps a separate option? (Should be on by
+    default, though, I think).
     """
     prefix = "tor"
 
-    def _parseClient(self, host=None, port=None, socksHostname=None, socksPort=None, socksUsername=None, socksPassword=None):
+    def _parseClient(self, host=None, port=None,
+                     socksHostname=None, socksPort=None,
+                     socksUsername=None, socksPassword=None):
         if port is not None:
             port = int(port)
         if socksHostname is None:
@@ -687,7 +724,11 @@ class TorClientEndpointStringParser(object):
         if socksPort is not None:
             socksPort = int(socksPort)
 
-        return TorClientEndpoint(host, port, socksHostname=socksHostname, socksPort=socksPort, socksUsername=socksUsername, socksPassword=socksPassword)
+        return TorClientEndpoint(
+            host, port,
+            socks_hostname=socksHostname, socks_port=socksPort,
+            socks_username=socksUsername, socks_password=socksPassword
+        )
 
     def parseStreamClient(self, *args, **kwargs):
         return self._parseClient(*args, **kwargs)
diff --git a/txtorcon/torstate.py b/txtorcon/torstate.py
index 8b0bb05..fd73992 100644
--- a/txtorcon/torstate.py
+++ b/txtorcon/torstate.py
@@ -188,7 +188,8 @@ class TorState(object):
     @classmethod
     def from_protocol(cls, protocol, **kw):
         '''
-        Create a new, boot-strapped TorState from a TorControlProtocol instance.
+        Create a new, boot-strapped TorState from a TorControlProtocol
+        instance.
 
         :return: a Deferred that fires with a TorState instance
         '''

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



More information about the Pkg-privacy-commits mailing list