[Pkg-privacy-commits] [txtorcon] 01/03: Imported Upstream version 0.16.1

Iain R. Learmonth irl at moszumanska.debian.org
Mon Sep 5 09:04:43 UTC 2016


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

irl pushed a commit to branch master
in repository txtorcon.

commit ac340d45f084f9cd8a04c63034900530aa8addfa
Author: Iain R. Learmonth <irl at fsfe.org>
Date:   Mon Sep 5 09:56:57 2016 +0100

    Imported Upstream version 0.16.1
---
 Makefile                        |   2 +-
 PKG-INFO                        |   2 +-
 docs/README.rst                 | 226 +---------------------------------------
 docs/release-checklist.rst      |   6 +-
 docs/releases.rst               |  19 +++-
 examples/gui-map.py             |  66 ++++++++++++
 examples/gui.py                 |  83 +++++++++++++++
 examples/gui2.py                |  52 +++++++++
 test/test_endpoints.py          | 217 ++++++++++++++++++++++++++------------
 test/test_torconfig.py          |  31 ++++++
 txtorcon.egg-info/PKG-INFO      |   2 +-
 txtorcon.egg-info/SOURCES.txt   |   3 +
 txtorcon.egg-info/top_level.txt |   2 +-
 txtorcon/_metadata.py           |   2 +-
 txtorcon/endpoints.py           | 128 ++++++++++++++---------
 txtorcon/torconfig.py           |  29 ++++++
 16 files changed, 521 insertions(+), 349 deletions(-)

diff --git a/Makefile b/Makefile
index 8a940bf..30162a8 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 .PHONY: test html counts coverage sdist clean install doc integration
 default: test
-VERSION = 0.15.1
+VERSION = 0.16.1
 
 test:
 	trial --reporter=text test
diff --git a/PKG-INFO b/PKG-INFO
index 06fe8f9..55a3f2d 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: txtorcon
-Version: 0.15.1
+Version: 0.16.1
 Summary: 
     Twisted-based Tor controller client, with state-tracking and
     configuration abstractions.
diff --git a/docs/README.rst b/docs/README.rst
deleted file mode 100644
index fca84b2..0000000
--- a/docs/README.rst
+++ /dev/null
@@ -1,225 +0,0 @@
-README
-======
-
-Documentation at https://txtorcon.readthedocs.org
-
-.. image:: https://travis-ci.org/meejah/txtorcon.png?branch=master
-    :target: https://www.travis-ci.org/meejah/txtorcon
-
-.. image:: https://coveralls.io/repos/meejah/txtorcon/badge.png
-    :target: https://coveralls.io/r/meejah/txtorcon
-
-.. image:: http://codecov.io/github/meejah/txtorcon/coverage.svg?branch=master
-    :target: http://codecov.io/github/meejah/txtorcon?branch=master
-
-.. image:: http://api.flattr.com/button/flattr-badge-large.png
-    :target: http://flattr.com/thing/1689502/meejahtxtorcon-on-GitHub
-
-
-quick start
------------
-
-For the impatient, there are two quick ways to install this::
-
-   $ pip install txtorcon
-
-... or, if you checked out or downloaded the source::
-
-   $ python setup.py install
-
-... or, better yet, use a virtualenv and the dev requirements::
-
-   $ virtualenv venv
-   $ ./venv/bin/pip install -e .[dev]
-
-For OSX, we can install txtorcon with the help of easy_install::
-
-   $ easy_install txtorcon
-
-To avoid installing, you can just add the base of the source to your
-PYTHONPATH::
-
-   $ export PYTHONPATH=`pwd`:$PYTHONPATH
-
-Then, you will want to explore the examples. Try "python
-examples/stream\_circuit\_logger.py" for instance.
-
-On Debian testing (jessie), or with wheezy-backports (big thanks to
-Lunar^ for all his packaging work) you can install easily::
-
-    $ apt-get install python-txtorcon
-
-You may also like `this asciinema demo <http://asciinema.org/a/5654>`_
-for an overview.
-
-Tor configuration
------------------
-
-You'll want to have the following options on in your ``torrc``::
-
-   CookieAuthentication 1
-   CookieAuthFileGroupReadable 1
-
-If you want to use unix sockets to speak to tor::
-
-   ControlSocketsGroupWritable 1
-   ControlSocket /var/run/tor/control
-
-The defaults used by py:meth:`txtorcon.build_local_tor_connection` will
-find a Tor on ``9051`` or ``/var/run/tor/control``
-
-
-overview
---------
-
-txtorcon is a Twisted-based asynchronous Tor control protocol
-implementation. Twisted is an event-driven networking engine written
-in Python and Tor is an onion-routing network designed to improve
-people's privacy and anonymity on the Internet.
-
-The main abstraction of this library is txtorcon.TorControlProtocol
-which presents an asynchronous API to speak the Tor client protocol in
-Python. txtorcon also provides abstractions to track and get updates
-about Tor's state (txtorcon.TorState) and current configuration
-(including writing it to Tor or disk) in txtorcon.TorConfig, along
-with helpers to asynchronously launch slave instances of Tor including
-Twisted endpoint support.
-
-txtorcon runs all tests cleanly on:
-
--  Debian "squeeze", "wheezy" and "jessie"
--  OS X 10.4 (naif)
--  OS X 10.8 (lukas lueg)
--  OS X 10.9 (kurt neufeld)
--  Fedora 18 (lukas lueg)
--  FreeBSD 10 (enrique fynn) (**needed to install "lsof"**)
--  RHEL6
--  Reports from other OSes appreciated.
-
-If instead you want a synchronous (threaded) Python controller
-library, check out Stem at https://stem.torproject.org/
-
-
-quick implementation overview
------------------------------
-
-txtorcon provides a class to track Tor's current state -- such as
-details about routers, circuits and streams -- called
-txtorcon.TorState and an abstraction to the configuration values via
-txtorcon.TorConfig which provides attribute-style accessors to Tor's
-state (including making changes). txtorcon.TorState provides
-txtorcon.Router, txtorcon.Circuit and txtorcon.Stream objects which
-implement a listener interface so client code may receive updates (in
-real time) including Tor events.
-
-txtorcon uses **trial for unit-tests** and has 100% test-coverage --
-which is not to say I've covered all the cases, but nearly all of the
-code is at least exercised somehow by the unit tests.
-
-Tor itself is not required to be running for any of the tests. ohcount
-claims around 2000 lines of code for the core bit; around 4000
-including tests. About 37% comments in the not-test code.
-
-There are a few simple integration tests, based on Docker. More are
-always welcome!
-
-
-dependencies / requirements
----------------------------
-
-- `twisted <http://twistedmatrix.com>`_: txtorcon should work with any
-   Twisted 11.1.0 or newer. Twisted 15.4.0+ works with Python3, and so
-   does txtorcon (if you find something broken on Py3 please file a bug).
-
--  `GeoIP <https://www.maxmind.com/app/python>`_: **optional** provides location
-   information for ip addresses; you will want to download GeoLite City
-   from `MaxMind <https://www.maxmind.com/app/geolitecity>`_ or pay them
-   for more accuracy. Or use tor-geoip, which makes this sort-of
-   optional, in that we'll query Tor for the IP if the GeoIP database
-   doesn't have an answer. It also does ASN lookups if you installed that MaxMind database.
-
--  development: `Sphinx <http://sphinx.pocoo.org/>`_ if you want to build the
-   documentation. In that case you'll also need something called
-   ``python-repoze.sphinx.autointerface`` (at least in Debian) to build
-   the Interface-derived docs properly.
-
--  development: `coverage <http://nedbatchelder.com/code/coverage/>`_ to
-   run the code-coverage metrics, and Tox
-
--  optional: GraphViz is used in the tests (and to generate state-machine
-   diagrams, if you like) but those tests are skipped if "dot" isn't
-   in your path
-
-.. BEGIN_INSTALL
-
-In any case, on a `Debian <http://www.debian.org/>`_ wheezy, squeeze or
-Ubuntu system, this should work::
-
-    apt-get install -y python-setuptools python-twisted python-ipaddr python-geoip graphviz tor
-    apt-get install -y python-sphinx python-repoze.sphinx.autointerface python-coverage # for development
-
-.. END_INSTALL
-
-Using pip this would be::
-
-    pip install Twisted ipaddr pygeoip
-    pip install GeoIP Sphinx repoze.sphinx.autointerface coverage  # for development
-
-or::
-
-    pip install -r requirements.txt
-    pip install -r dev-requirements.txt
-
-or for the bare minimum::
-
-    pip install Twisted  # will install zope.interface too
-
-
-documentation
--------------
-
-It is likely that you will need to read at least some of
-`control-spec.txt <https://gitweb.torproject.org/torspec.git/blob/HEAD:/control-spec.txt>`_
-from the torspec git repository so you know what's being abstracted by
-this library.
-
-Run "make doc" to build the Sphinx documentation locally, or rely on
-ReadTheDocs https://txtorcon.readthedocs.org which builds each tagged
-release and the latest master.
-
-There is also a directory of examples/ scripts, which have inline
-documentation explaining their use.
-
-
-contact information
--------------------
-
-For novelty value, the Web site (with built documentation and so forth)
-can be viewed via Tor at http://timaq4ygg2iegci7.onion although the
-code itself is hosted via git::
-
-    torsocks git clone git://timaq4ygg2iegci7.onion/txtorcon.git
-
-or::
-
-    git clone git://github.com/meejah/txtorcon.git
-
-You may contact me via ``meejah at meejah dot ca`` with GPG key
-`0xC2602803128069A7
-<http://pgp.mit.edu:11371/pks/lookup?op=get&search=0xC2602803128069A7>`_
-or see ``meejah.asc`` in the repository. The fingerprint is ``9D5A
-2BD5 688E CB88 9DEB CD3F C260 2803 1280 69A7``.
-
-It is often possible to contact me as ``meejah`` in #tor-dev on `OFTC
-<http://www.oftc.net/oftc/>`_ but be patient for replies (I do look at
-scrollback, so putting "meejah: " in front will alert my client).
-
-More conventionally, you may get the code at GitHub and documentation
-via ReadTheDocs:
-
--  https://github.com/meejah/txtorcon
--  https://txtorcon.readthedocs.org
-
-Please do **use the GitHub issue-tracker** to report bugs. Patches,
-pull-requests, comments and criticisms are all welcomed and
-appreciated.
diff --git a/docs/README.rst b/docs/README.rst
new file mode 120000
index 0000000..89a0106
--- /dev/null
+++ b/docs/README.rst
@@ -0,0 +1 @@
+../README.rst
\ No newline at end of file
diff --git a/docs/release-checklist.rst b/docs/release-checklist.rst
index 4373d8c..e70bc99 100644
--- a/docs/release-checklist.rst
+++ b/docs/release-checklist.rst
@@ -1,6 +1,10 @@
 Release Checklist
 =================
 
+* ensure local copy is on master, up-to-date:
+   * git checkout master
+   * git pull
+
 * double-check version updated, sadly in a few places:
    * Makefile
    * txtorcon/_metadata.py
@@ -15,7 +19,7 @@ Release Checklist
    * update heading, date
 
 * on both signing-machine and build-machine shells:
-   * export VERSION=v0.15.1
+   * export VERSION=0.16.1
 
 * (if on signing machine) "make dist" and "make dist-sig"
    * creates:
diff --git a/docs/releases.rst b/docs/releases.rst
index 3f82ace..459a84b 100644
--- a/docs/releases.rst
+++ b/docs/releases.rst
@@ -19,7 +19,24 @@ Rendered docs on `txtorcon.readthedocs <http://txtorcon.readthedocs.io/en/releas
 unreleased
 ----------
 
-`git master <https://github.com/meejah/txtorcon>`_ *will likely become v0.16.0*
+`git master <https://github.com/meejah/txtorcon>`_ *will likely become v0.17.0*
+
+
+v0.16.1
+-------
+
+*August 31, 2016*
+
+ * `txtorcon-0.16.1.tar.gz <http://timaq4ygg2iegci7.onion/txtorcon-0.16.1.tar.gz>`_ (`PyPI <https://pypi.python.org/pypi/txtorcon/0.16.1>`_ (:download:`local-sig </../signatues/txtorcon-0.16.1.tar.gz.asc>` or `github-sig <https://github.com/meejah/txtorcon/blob/master/signatues/txtorcon-0.16.1.tar.gz.asc?raw=true>`_) (`source <https://github.com/meejah/txtorcon/archive/v0.16.1.tar.gz>`_)
+ * `issue 172 <https://github.com/meejah/txtorcon/issues/172>`_: give `TorProcessProtocol` a `.quit` method
+ * `issue 181 <https://github.com/meejah/txtorcon/issues/181>`_: enable SOCKS5-over-unix-sockets for TorClientEndpoint (thanks to `david415 <https://github.com/david415>`_
+
+
+v0.16.0
+-------
+
+ * there wasn't one, `because reasons <https://github.com/meejah/txtorcon/commit/e4291c01ff223d3cb7774437cafa2f06ca195bcf>`_.
+
 
 v0.15.1
 -------
diff --git a/examples/gui-map.py b/examples/gui-map.py
new file mode 100644
index 0000000..9b895d4
--- /dev/null
+++ b/examples/gui-map.py
@@ -0,0 +1,66 @@
+# fooling around w/ a GUI
+
+# need to boot up GTK first
+try:
+    import pgi
+    pgi.install_as_gi()
+except ImportError:
+    pass
+import gi
+gi.require_version('Gtk', '3.0')
+
+# ...then "really soon" (before any other twisted reactor imports) get
+# the correct reactor
+
+from twisted.internet import gtk3reactor
+gtk3reactor.install()
+
+# normal imports follow
+
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred, inlineCallbacks
+from twisted.internet.task import react
+
+from twisted.internet.endpoints import TCP4ClientEndpoint
+import txtorcon
+
+from gi.repository import Gtk
+
+def draw_area(widget, cr):
+    print("ding", widget, cr)
+    for x in dir(widget):
+        print(x)
+    print("ddd", widget.get_size_request())
+    print("XXX", dir(widget.get_allocation()))
+    w, h = widget.get_allocation().width, widget.get_allocation().height
+    cr.set_source_rgb(0, 0, 0)
+    cr.set_line_width(1.0)
+
+    for lng in range(0, 360, 5):
+        for lat in range(-160, 160, 5):
+            x, y = lng * 2, (lat + 160) * 2
+            cr.rectangle(x + 10, y + 10, 6, 6)
+            cr.stroke()
+
+def create_win():
+    win = Gtk.Window()
+    win.connect("delete-event", Gtk.main_quit)
+
+    area = Gtk.DrawingArea()
+    area.set_size_request(320, 240)
+    area.connect('draw', draw_area)
+    win.add(area)
+    return win
+
+app = Gtk.Application()
+reactor.registerGApplication(app)
+
+ at inlineCallbacks
+def main(reactor):
+    ep = TCP4ClientEndpoint(reactor, "localhost", 9051)
+    tor = yield txtorcon.connect(reactor, ep)
+    win = create_win()
+    win.show_all()
+    app.add_window(win)
+    yield Deferred()
+react(main)
diff --git a/examples/gui.py b/examples/gui.py
new file mode 100644
index 0000000..7c9082f
--- /dev/null
+++ b/examples/gui.py
@@ -0,0 +1,83 @@
+# fooling around w/ a GUI
+
+# need to boot up GTK first
+try:
+    import pgi
+    pgi.install_as_gi()
+except ImportError:
+    pass
+import gi
+gi.require_version('Gtk', '3.0')
+
+# ...then "really soon" (before any other twisted reactor imports) get
+# the correct reactor
+
+from twisted.internet import gtk3reactor
+gtk3reactor.install()
+
+# normal imports follow
+
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred, inlineCallbacks
+from twisted.internet.task import react
+
+from twisted.internet.endpoints import TCP4ClientEndpoint
+import txtorcon
+
+from gi.repository import Gtk
+
+def create_win():
+    #win = Gtk.Window()
+    win = Gtk.Dialog()
+    win.connect("delete-event", lambda a, b: reactor.stop())#Gtk.main_quit)
+    win.set_border_width(10)
+    win.set_default_size(320, 220)
+
+    def got_response(dialog, response_code):
+        if response_code == 12:
+            print("Cancelling; Tor not launched")
+            reactor.stop()
+    win.connect("response", got_response)
+
+#    cancel = Gtk.Button.new_with_label("Cancel")
+
+    #vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
+    vbox = win.get_content_area()
+    win.add(vbox)
+    win.add_button('Cancel', 12)
+    progress = Gtk.ProgressBar()
+    progress.set_property('show-text', True)
+    progress.set_show_text(True)
+    progress.set_text(None)
+    label = Gtk.Label("Launching Tor")
+    log = Gtk.Label("")
+
+    messages = []
+    def progress_updates(prog, tag, msg):
+        progress.set_fraction(prog / 100.0)
+        if len(messages) == 0 or msg != messages[0]:
+            messages.insert(0, msg)
+        markup = ''
+        for n, msg in enumerate(messages[:6]):
+            percent = n / 6.0
+            val = '%x' % int(255 * percent)
+            markup += '<span foreground="#{0}{0}{0}">{1}</span>\n'.format(val, msg)
+        #log.set_text('\n'.join(messages))
+        log.set_markup(markup)
+    
+    vbox.pack_start(label, expand=False, fill=False, padding=5)
+    vbox.pack_start(progress, expand=False, fill=False, padding=0)
+    vbox.pack_start(log, expand=False, fill=False, padding=0)
+    vbox.pack_start(Gtk.Label(), expand=True, fill=True, padding=0)
+    return win, progress_updates
+
+ at inlineCallbacks
+def main(reactor):
+    ep = TCP4ClientEndpoint(reactor, "localhost", 9051)
+    win, prog = create_win()
+    win.show_all()
+    tor = yield txtorcon.launch(reactor, progress_updates=prog)
+    print("tor launched", tor)
+    win.destroy()
+    yield Deferred()
+react(main)
diff --git a/examples/gui2.py b/examples/gui2.py
new file mode 100644
index 0000000..e1c8eb6
--- /dev/null
+++ b/examples/gui2.py
@@ -0,0 +1,52 @@
+# fooling around w/ a GUI
+
+# need to boot up GTK first
+try:
+    import pgi
+    pgi.install_as_gi()
+except ImportError:
+    pass
+import gi
+gi.require_version('Gtk', '3.0')
+
+# ...then "really soon" (before any other twisted reactor imports) get
+# the correct reactor
+
+from twisted.internet import gtk3reactor
+gtk3reactor.install()
+
+# normal imports follow
+
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred, inlineCallbacks
+from twisted.internet.task import react
+
+from twisted.internet.endpoints import TCP4ClientEndpoint
+import txtorcon
+
+from gi.repository import Gtk
+
+def create_win():
+    win = Gtk.Window()
+    win.connect("delete-event", Gtk.main_quit)
+
+    vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
+    win.add(vbox)
+    progress = Gtk.ProgressBar()
+    label = Gtk.Label("no progress yet")
+    vbox.pack_start(progress, True, True, 0)
+    vbox.pack_start(label, True, True, 0)
+    return win
+
+app = Gtk.Application()
+reactor.registerGApplication(app)
+
+ at inlineCallbacks
+def main(reactor):
+    ep = TCP4ClientEndpoint(reactor, "localhost", 9051)
+    tor = yield txtorcon.connect(reactor, ep)
+#    win = create_main(tor)
+#    win.show_all()
+#    app.add_window(win)
+#    yield Deferred()
+react(main)
diff --git a/test/test_endpoints.py b/test/test_endpoints.py
index ab0c68a..d1eb0fb 100644
--- a/test/test_endpoints.py
+++ b/test/test_endpoints.py
@@ -11,6 +11,7 @@ from twisted.trial import unittest
 from twisted.test import proto_helpers
 from twisted.internet import defer, error, task, tcp
 from twisted.internet.endpoints import TCP4ServerEndpoint
+from twisted.internet.endpoints import TCP4ClientEndpoint
 from twisted.internet.endpoints import serverFromString
 from twisted.internet.endpoints import clientFromString
 from twisted.python.failure import Failure
@@ -34,7 +35,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
+from txtorcon.endpoints import _HAVE_TLS
 
 import util
 
@@ -592,10 +593,11 @@ class TestTorClientEndpoint(unittest.TestCase):
         This test is equivalent to txsocksx's
         TestSOCKS4ClientEndpoint.test_clientConnectionFailed
         """
-        def fail_tor_socks_endpoint_generator(*args, **kw):
-            kw['failure'] = Failure(ConnectionRefusedError())
-            return FakeTorSocksEndpoint(*args, **kw)
-        endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=fail_tor_socks_endpoint_generator)
+        args = "host123"
+        kw = dict()
+        kw['failure'] = Failure(ConnectionRefusedError())
+        tor_endpoint = FakeTorSocksEndpoint(*args, **kw)
+        endpoint = TorClientEndpoint('', 0, socks_endpoint=tor_endpoint)
         d = endpoint.connect(None)
         return self.assertFailure(d, ConnectionRefusedError)
 
@@ -603,20 +605,17 @@ class TestTorClientEndpoint(unittest.TestCase):
         """
         Same as above, but with a username/password.
         """
-        def fail_tor_socks_endpoint_generator(*args, **kw):
-            kw['failure'] = Failure(ConnectionRefusedError())
-            return FakeTorSocksEndpoint(*args, **kw)
+        args = "fakehost"
+        kw = dict()
+        kw['failure'] = Failure(ConnectionRefusedError())
+        tor_endpoint = FakeTorSocksEndpoint(*args, **kw)
         endpoint = TorClientEndpoint(
             'invalid host', 0,
             socks_username='billy', socks_password='s333cure',
-            _proxy_endpoint_generator=fail_tor_socks_endpoint_generator)
+            socks_endpoint = tor_endpoint)
         d = endpoint.connect(None)
         return self.assertFailure(d, ConnectionRefusedError)
 
-    def test_default_generator(self):
-        # just ensuring the default generator doesn't blow up
-        default_tcp4_endpoint_generator(None, 'foo.bar', 1234)
-
     def test_no_host(self):
         self.assertRaises(
             ValueError,
@@ -628,7 +627,8 @@ class TestTorClientEndpoint(unittest.TestCase):
 
         self.assertEqual(ep.host, 'timaq4ygg2iegci7.onion')
         self.assertEqual(ep.port, 80)
-        self.assertEqual(ep.socks_port, 9050)
+        # XXX what's "the Twisted way" to get the port out here?
+        self.assertEqual(ep.socks_endpoint._port, 9050)
 
     def test_parser_user_password(self):
         epstring = 'tor:host=torproject.org:port=443' + \
@@ -644,15 +644,13 @@ class TestTorClientEndpoint(unittest.TestCase):
         """
         This test is equivalent to txsocksx's TestSOCKS5ClientEndpoint.test_defaultFactory
         """
-        endpoints = []
 
-        def tor_socks_endpoint_generator(*args, **kw):
-            endpoints.append(FakeTorSocksEndpoint(*args, **kw))
-            return endpoints[-1]
-        endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=tor_socks_endpoint_generator)
+        args = "fakehost"
+        kw = dict()
+        tor_endpoint = FakeTorSocksEndpoint(*args, **kw)
+        endpoint = TorClientEndpoint('', 0, socks_endpoint=tor_endpoint)
         endpoint.connect(Mock)
-        self.assertEqual(1, len(endpoints))
-        self.assertEqual(endpoints[0].transport.value(), '\x05\x01\x00')
+        self.assertEqual(tor_endpoint.transport.value(), '\x05\x01\x00')
 
     @patch('txtorcon.endpoints.SOCKS5ClientEndpoint')
     @defer.inlineCallbacks
@@ -661,11 +659,10 @@ class TestTorClientEndpoint(unittest.TestCase):
         gold_proto = object()
         ep.connect = MagicMock(return_value=gold_proto)
         socks5_factory.return_value = ep
-
-        def tor_socks_endpoint_generator(*args, **kw):
-            return FakeTorSocksEndpoint(*args, **kw)
-
-        endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=tor_socks_endpoint_generator)
+        args = "fakehost"
+        kw = dict()
+        tor_endpoint = FakeTorSocksEndpoint(*args, **kw)
+        endpoint = TorClientEndpoint('', 0, socks_endpoint = tor_endpoint)
         other_proto = yield endpoint.connect(MagicMock())
         self.assertEqual(other_proto, gold_proto)
 
@@ -677,16 +674,16 @@ class TestTorClientEndpoint(unittest.TestCase):
         proxy endpoint for each port that the Tor client endpoint will try.
         """
         success_ports = TorClientEndpoint.socks_ports_to_try
-        endpoints = []
         for port in success_ports:
-            def tor_socks_endpoint_generator(*args, **kw):
-                kw['accept_port'] = port
-                kw['failure'] = Failure(ConnectionRefusedError())
-                endpoints.append(FakeTorSocksEndpoint(*args, **kw))
-                return endpoints[-1]
-            endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=tor_socks_endpoint_generator)
+            tor_endpoint = FakeTorSocksEndpoint(
+                "fakehost", "127.0.0.1", port,
+                accept_port=port,
+                failure=Failure(ConnectionRefusedError()),
+            )
+
+            endpoint = TorClientEndpoint('', 0, socks_endpoint=tor_endpoint)
             endpoint.connect(None)
-            self.assertEqual(endpoints[-1].transport.value(), '\x05\x01\x00')
+            self.assertEqual(tor_endpoint.transport.value(), '\x05\x01\x00')
 
     def test_bad_port_retry(self):
         """
@@ -694,41 +691,131 @@ class TestTorClientEndpoint(unittest.TestCase):
         """
         fail_ports = [1984, 666]
         for port in fail_ports:
-            def tor_socks_endpoint_generator(*args, **kw):
-                kw['accept_port'] = port
-                kw['failure'] = Failure(ConnectionRefusedError())
-                return FakeTorSocksEndpoint(*args, **kw)
-            endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=tor_socks_endpoint_generator)
+            ep = FakeTorSocksEndpoint(
+                '', '', 0,
+                accept_port=port,
+                failure=Failure(ConnectionRefusedError()),
+            )
+            endpoint = TorClientEndpoint('', 0, socks_endpoint=ep)
             d = endpoint.connect(None)
             return self.assertFailure(d, ConnectionRefusedError)
 
-    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.
-        """
-        endpoints = []
-
-        def tor_socks_endpoint_generator(*args, **kw):
-            kw['accept_port'] = 6669
-            kw['failure'] = Failure(ConnectionRefusedError())
-            endpoints.append(FakeTorSocksEndpoint(*args, **kw))
-            return endpoints[-1]
-        endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=tor_socks_endpoint_generator, socks_port=6669)
-        endpoint.connect(None)
-        self.assertEqual(1, len(endpoints))
-        self.assertEqual(endpoints[-1].transport.value(), '\x05\x01\x00')
-
-    def test_bad_no_guess_socks_port(self):
+    @patch('txtorcon.endpoints.SOCKS5ClientEndpoint')
+    def test_default_socks_ports_fails(self, ep_mock):
         """
-        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
-        the socks_ports_to_try list.
+        Ensure we iterate over the default socks ports
         """
-        def tor_socks_endpoint_generator(*args, **kw):
-            kw['accept_port'] = 9050
-            kw['failure'] = Failure(ConnectionRefusedError())
-            return FakeTorSocksEndpoint(*args, **kw)
-        endpoint = TorClientEndpoint('', 0, _proxy_endpoint_generator=tor_socks_endpoint_generator, socks_port=6669)
+
+        class FakeSocks5(object):
+
+            def __init__(self, *args, **kw):
+                pass
+
+            def connect(self, *args, **kw):
+                raise ConnectionRefusedError()
+
+        ep_mock.side_effect = FakeSocks5
+        endpoint = TorClientEndpoint('', 0)#, socks_endpoint=ep)
         d = endpoint.connect(None)
         self.assertFailure(d, ConnectionRefusedError)
+
+    @patch('txtorcon.endpoints.SOCKS5ClientEndpoint')
+    @defer.inlineCallbacks
+    def test_default_socks_ports_happy(self, ep_mock):
+        """
+        Ensure we iterate over the default socks ports
+        """
+
+        proto = object()
+        class FakeSocks5(object):
+
+            def __init__(self, *args, **kw):
+                pass
+
+            def connect(self, *args, **kw):
+                return proto
+
+        ep_mock.side_effect = FakeSocks5
+        endpoint = TorClientEndpoint('', 0)
+        p2 = yield endpoint.connect(None)
+        self.assertTrue(proto is p2)
+
+    @patch('txtorcon.endpoints.SOCKS5ClientEndpoint')
+    @defer.inlineCallbacks
+    def test_tls_socks_no_endpoint(self, ep_mock):
+
+        if not _HAVE_TLS:
+            print("no TLS support")
+            return
+
+        class FakeWrappedProto(object):
+            wrappedProtocol = object()
+
+        wrap = FakeWrappedProto()
+        proto = defer.succeed(wrap)
+        class FakeSocks5(object):
+
+            def __init__(self, *args, **kw):
+                pass
+
+            def connect(self, *args, **kw):
+                return proto
+
+        ep_mock.side_effect = FakeSocks5
+        endpoint = TorClientEndpoint('torproject.org', 0, tls=True)
+        p2 = yield endpoint.connect(None)
+        self.assertTrue(wrap.wrappedProtocol is p2)
+
+    @patch('txtorcon.endpoints.SOCKS5ClientEndpoint')
+    @defer.inlineCallbacks
+    def test_tls_socks_with_endpoint(self, ep_mock):
+        """
+        Same as above, except we provide an explicit endpoint
+        """
+
+        if not _HAVE_TLS:
+            print("no TLS support")
+            return
+
+        class FakeWrappedProto(object):
+            wrappedProtocol = object()
+
+        wrap = FakeWrappedProto()
+        proto = defer.succeed(wrap)
+        class FakeSocks5(object):
+
+            def __init__(self, *args, **kw):
+                pass
+
+            def connect(self, *args, **kw):
+                return proto
+
+        ep_mock.side_effect = FakeSocks5
+        endpoint = TorClientEndpoint(
+            'torproject.org', 0,
+            socks_endpoint=clientFromString(Mock(), "tcp:localhost:9050"),
+            tls=True,
+        )
+        p2 = yield endpoint.connect(None)
+        self.assertTrue(wrap.wrappedProtocol is p2)
+
+    @patch('txtorcon.endpoints.reactor')  # FIXME should be passing reactor to TorClientEndpoint :/
+    def test_client_endpoint_old_api(self, reactor):
+        """
+        Test the old API of passing socks_host, socks_port
+        """
+
+        endpoint = TorClientEndpoint(
+            'torproject.org', 0,
+            socks_host='localhost',
+            socks_port=9050,
+        )
+        self.assertTrue(isinstance(endpoint.socks_endpoint, TCP4ClientEndpoint))
+
+        d = endpoint.connect(Mock())
+        calls = reactor.mock_calls
+        self.assertEqual(1, len(calls))
+        name, args, kw = calls[0]
+        self.assertEqual("connectTCP", name)
+        self.assertEqual("localhost", args[0])
+        self.assertEqual(9050, args[1])
diff --git a/test/test_torconfig.py b/test/test_torconfig.py
index b0edea0..5869860 100644
--- a/test/test_torconfig.py
+++ b/test/test_torconfig.py
@@ -1611,6 +1611,37 @@ ControlPort Port''')
         process.progress(10, 'tag', 'summary')
         self.assertTrue(self.got_progress)
 
+    def test_quit_process(self):
+        process = TorProcessProtocol(None)
+        process.transport = Mock()
+
+        d = process.quit()
+        self.assertFalse(d.called)
+
+        process.processExited(Failure(error.ProcessTerminated(exitCode=15)))
+        self.assertTrue(d.called)
+        process.processEnded(Failure(error.ProcessDone(None)))
+        self.assertTrue(d.called)
+        errs = self.flushLoggedErrors()
+        self.assertEqual(1, len(errs))
+        self.assertTrue("Tor exited with error-code" in str(errs[0]))
+
+    def test_quit_process_already(self):
+        process = TorProcessProtocol(None)
+        process.transport = Mock()
+
+        def boom(sig):
+            self.assertEqual(sig, 'TERM')
+            raise error.ProcessExitedAlready()
+        process.transport.signalProcess = Mock(side_effect=boom)
+
+        d = process.quit()
+        process.processEnded(Failure(error.ProcessDone(None)))
+        self.assertTrue(d.called)
+        errs = self.flushLoggedErrors()
+        self.assertEqual(1, len(errs))
+        self.assertTrue("Tor exited with error-code" in str(errs[0]))
+
     def test_status_updates(self):
         process = TorProcessProtocol(None)
         process.status_client("NOTICE CONSENSUS_ARRIVED")
diff --git a/txtorcon.egg-info/PKG-INFO b/txtorcon.egg-info/PKG-INFO
index 06fe8f9..55a3f2d 100644
--- a/txtorcon.egg-info/PKG-INFO
+++ b/txtorcon.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: txtorcon
-Version: 0.15.1
+Version: 0.16.1
 Summary: 
     Twisted-based Tor controller client, with state-tracking and
     configuration abstractions.
diff --git a/txtorcon.egg-info/SOURCES.txt b/txtorcon.egg-info/SOURCES.txt
index 4255aed..50ee52a 100644
--- a/txtorcon.egg-info/SOURCES.txt
+++ b/txtorcon.egg-info/SOURCES.txt
@@ -50,6 +50,9 @@ examples/circuit_for_next_stream.py
 examples/disallow_streams_by_port.py
 examples/dump_config.py
 examples/ephemeral_endpoint.py
+examples/gui-map.py
+examples/gui.py
+examples/gui2.py
 examples/hello_darkweb.py
 examples/hidden-service-systemd.service
 examples/launch_tor.py
diff --git a/txtorcon.egg-info/top_level.txt b/txtorcon.egg-info/top_level.txt
index dc51759..1ba11e2 100644
--- a/txtorcon.egg-info/top_level.txt
+++ b/txtorcon.egg-info/top_level.txt
@@ -1,2 +1,2 @@
-twisted
 txtorcon
+twisted
diff --git a/txtorcon/_metadata.py b/txtorcon/_metadata.py
index 3e2b124..c7f7af5 100644
--- a/txtorcon/_metadata.py
+++ b/txtorcon/_metadata.py
@@ -1,4 +1,4 @@
-__version__ = '0.15.1'
+__version__ = '0.16.1'
 __author__ = 'meejah'
 __contact__ = 'meejah at meejah.ca'
 __url__ = 'https://github.com/meejah/txtorcon'
diff --git a/txtorcon/endpoints.py b/txtorcon/endpoints.py
index a901555..e36e217 100644
--- a/txtorcon/endpoints.py
+++ b/txtorcon/endpoints.py
@@ -22,6 +22,14 @@ except ImportError:
     from twisted.internet.interfaces import IStreamClientEndpointStringParser as IStreamClientEndpointStringParserWithReactor
     _HAVE_TX_14 = False
 
+try:
+    from twisted.internet.ssl import optionsForClientTLS
+    from txsocksx.tls import TLSWrapClientEndpoint
+    _HAVE_TLS = True
+except ImportError:
+    _HAVE_TLS = False
+
+
 from twisted.internet import defer, reactor
 from twisted.python import log
 from twisted.internet.interfaces import IStreamServerEndpointStringParser
@@ -633,89 +641,101 @@ class TCPHiddenServiceEndpointParser(object):
                                                    control_port=controlPort)
 
 
-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...
-    """
-    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.
-
-    :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.
+    connection with the system tor process. If no socks_endpoint is
+    given, I will try TCP4 to localhost on ports 9050 then 9150.
 
-    :param _proxy_endpoint_generator: This is used for unit tests.
+    :param socks_endpoint:
+        An IStreamClientEndpoint that will connect to a SOCKS5
+        port. Tor can speak SOCKS5 over either TCP4 or Unix sockets.
 
-    :param socks_port:
-       This optional argument lets the user specify which Tor SOCKS
-       port should be used.
+    :param tls:
+        If True, we will attemp TLS negotiation after the SOCKS forwarding
+        is set up.
     """
     # XXX should get these via the control connection, i.e. ask Tor
     # via GETINFO net/listeners/socks or whatever
     socks_ports_to_try = [9050, 9150]
 
     def __init__(self, host, port,
-                 socks_hostname=None, socks_port=None,
+                 socks_endpoint=None,
                  socks_username=None, socks_password=None,
-                 _proxy_endpoint_generator=default_tcp4_endpoint_generator):
+                 tls=False, **kw):
         if host is None or port is None:
             raise ValueError('host and port must be specified')
 
         self.host = host
         self.port = int(port)
-        self._proxy_endpoint_generator = _proxy_endpoint_generator
-        self.socks_hostname = socks_hostname
-        self.socks_port = int(socks_port) if socks_port is not None else None
+        self.socks_endpoint = socks_endpoint
         self.socks_username = socks_username
         self.socks_password = socks_password
+        self.tls = tls
 
-        if self.socks_port is None:
+        if self.tls and not _HAVE_TLS:
+            raise ValueError(
+                "'tls=True' but we don't have TLS support"
+            )
+
+        # backwards-compatibility: you used to specify a TCP SOCKS
+        # endpoint via socks_host= and socks_port= kwargs
+        if self.socks_endpoint is None:
+            try:
+                self.socks_endpoint = TCP4ClientEndpoint(reactor, kw['socks_host'], kw['socks_port'])
+                # XXX should deprecation-warn here
+            except KeyError:
+                pass
+
+        # this is a separate "if" from above in case socks_endpoint
+        # was None but the user specified the (old)
+        # socks_host/socks_port (in which case we do NOT want
+        # guessing_enabled
+        if self.socks_endpoint is None:
             self._socks_port_iter = iter(self.socks_ports_to_try)
             self._socks_guessing_enabled = True
         else:
-            self._socks_port_iter = [socks_port]
             self._socks_guessing_enabled = False
 
     @defer.inlineCallbacks
     def connect(self, protocolfactory):
         last_error = None
-        for socks_port in self._socks_port_iter:
-            self.socks_port = socks_port
-            tor_ep = self._proxy_endpoint_generator(
-                reactor,
-                self.socks_hostname,
-                self.socks_port,
+        kwargs = dict()
+        if self.socks_username is not None and self.socks_password is not None:
+            kwargs['methods'] = dict(
+                login=(self.socks_username, self.socks_password),
             )
-
-            args = (self.host, self.port, tor_ep)
-            kwargs = dict()
-            if self.socks_username is not None and self.socks_password is not None:
-                kwargs['methods'] = dict(
-                    login=(self.socks_username, self.socks_password),
-                )
-
+        if self.socks_endpoint is not None:
+            args = (self.host, self.port, self.socks_endpoint)
             socks_ep = SOCKS5ClientEndpoint(*args, **kwargs)
+            if self.tls:
+                context = optionsForClientTLS(unicode(self.host))
+                socks_ep = TLSWrapClientEndpoint(context, socks_ep)
+            proto = yield socks_ep.connect(protocolfactory)
+            defer.returnValue(proto)
+        else:
+            for socks_port in self._socks_port_iter:
+                tor_ep = TCP4ClientEndpoint(
+                    reactor,
+                    "127.0.0.1",
+                    socks_port,
+                )
+                args = (self.host, self.port, tor_ep)
+                socks_ep = SOCKS5ClientEndpoint(*args, **kwargs)
+                if self.tls:
+                    # XXX only twisted 14+
+                    context = optionsForClientTLS(unicode(self.host))
+                    socks_ep = TLSWrapClientEndpoint(context, socks_ep)
 
-            try:
-                proto = yield socks_ep.connect(protocolfactory)
-                defer.returnValue(proto)
+                try:
+                    proto = yield socks_ep.connect(protocolfactory)
+                    defer.returnValue(proto)
 
-            except error.ConnectError as e0:
-                last_error = e0
-        if last_error is not None:
-            raise last_error
+                except error.ConnectError as e0:
+                    last_error = e0
+            if last_error is not None:
+                raise last_error
 
 
 @implementer(IPlugin, IStreamClientEndpointStringParserWithReactor)
@@ -762,10 +782,14 @@ class TorClientEndpointStringParser(object):
         if socksPort is not None:
             socksPort = int(socksPort)
 
+        ep = None
+        if socksPort is not None:
+            ep = TCP4ClientEndpoint(reactor, socksHostname, socksPort)
         return TorClientEndpoint(
             host, port,
-            socks_hostname=socksHostname, socks_port=socksPort,
-            socks_username=socksUsername, socks_password=socksPassword
+            socks_endpoint=ep,
+            socks_username=socksUsername,
+            socks_password=socksPassword,
         )
 
     def parseStreamClient(self, *args, **kwargs):
diff --git a/txtorcon/torconfig.py b/txtorcon/torconfig.py
index faf9e86..bfec9a8 100644
--- a/txtorcon/torconfig.py
+++ b/txtorcon/torconfig.py
@@ -115,6 +115,7 @@ class TorProcessProtocol(protocol.ProcessProtocol):
         self._setup_complete = False
         self._did_timeout = False
         self._timeout_delayed_call = None
+        self._on_exit = []  # Deferred's we owe a call/errback to when we exit
         if timeout:
             if not ireactortime:
                 raise RuntimeError(
@@ -124,6 +125,30 @@ class TorProcessProtocol(protocol.ProcessProtocol):
             self._timeout_delayed_call = ireactortime.callLater(
                 timeout, self.timeout_expired)
 
+    def quit(self):
+        """
+        This will terminate (with SIGTERM) the underlying Tor process.
+
+        :returns: a Deferred that callback()'s (with None) when the
+            process has actually exited.
+        """
+
+        try:
+            self.transport.signalProcess('TERM')
+            d = defer.Deferred()
+            self._on_exit.append(d)
+
+        except error.ProcessExitedAlready:
+            self.transport.loseConnection()
+            d = defer.succeed(None)
+        return d
+
+    def _signal_on_exit(self, reason):
+        to_notify = self._on_exit
+        self._on_exit = []
+        for d in to_notify:
+            d.callback(None)
+
     def outReceived(self, data):
         """
         :api:`twisted.internet.protocol.ProcessProtocol <ProcessProtocol>` API
@@ -179,6 +204,9 @@ class TorProcessProtocol(protocol.ProcessProtocol):
         all([delete_file_or_tree(f) for f in self.to_delete])
         self.to_delete = []
 
+    def processExited(self, reason):
+        self._signal_on_exit(reason)
+
     def processEnded(self, status):
         """
         :api:`twisted.internet.protocol.ProcessProtocol <ProcessProtocol>` API
@@ -201,6 +229,7 @@ class TorProcessProtocol(protocol.ProcessProtocol):
         if self.connected_cb:
             self.connected_cb.errback(err)
             self.connected_cb = None
+        self._signal_on_exit(status)
 
     def progress(self, percent, tag, summary):
         """

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