Hello community, here is the log from the commit of package python-txtorcon for openSUSE:Factory checked in at 2019-09-27 14:48:38 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-txtorcon (Old) and /work/SRC/openSUSE:Factory/.python-txtorcon.new.2352 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-txtorcon" Fri Sep 27 14:48:38 2019 rev:5 rq:731285 version:19.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-txtorcon/python-txtorcon.changes 2019-07-31 14:28:06.638167724 +0200 +++ /work/SRC/openSUSE:Factory/.python-txtorcon.new.2352/python-txtorcon.changes 2019-09-27 14:48:43.744720977 +0200 @@ -1,0 +2,16 @@ +Mon Sep 16 13:02:45 UTC 2019 - Tomáš Chvátal <tchva...@suse.com> + +- Update to 19.1.0: + * TorControlProtocol.on_disconnect is deprecated in favour of + TorControlProtocol.when_disconnected + * introduce non_anonymous_mode= kwarg in txtorcon.launch() + enabling Tor options making Onion Services non-anonymous for the + server (but they use a single hop instead of three to the + Introduction Point so they're slightly faster). + * add an API to listen to individual circuit and stream events + (without subclassing anything). Can be used as decorators too. + See e.g. TorState.on_circuit_new() +- Drop merged patch: + * python-txtorcon-methods-are-bytes.patch + +------------------------------------------------------------------- Old: ---- python-txtorcon-methods-are-bytes.patch txtorcon-19.0.0.tar.gz New: ---- txtorcon-19.1.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-txtorcon.spec ++++++ --- /var/tmp/diff_new_pack.TDYmXN/_old 2019-09-27 14:48:44.356719386 +0200 +++ /var/tmp/diff_new_pack.TDYmXN/_new 2019-09-27 14:48:44.356719386 +0200 @@ -18,15 +18,13 @@ %{?!python_module:%define python_module() python-%{**} %{!?skip_python3:python3-%{**}}} Name: python-txtorcon -Version: 19.0.0 +Version: 19.1.0 Release: 0 Summary: Twisted-based asynchronous Tor control protocol implementation License: MIT Group: Development/Languages/Python URL: https://txtorcon.readthedocs.org Source: https://files.pythonhosted.org/packages/source/t/txtorcon/txtorcon-%{version}.tar.gz -# https://github.com/meejah/txtorcon/commit/5d7ebea5086f361efe7f14aea58e512a04b401f3 -Patch0: python-txtorcon-methods-are-bytes.patch BuildRequires: %{python_module setuptools >= 36.2} BuildRequires: fdupes BuildRequires: python-ipaddress @@ -56,7 +54,6 @@ %prep %setup -q -n txtorcon-%{version} -%patch0 -p1 sed -i '/data_files/,/\]\,/s/^/#/' setup.py ++++++ txtorcon-19.0.0.tar.gz -> txtorcon-19.1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/Makefile new/txtorcon-19.1.0/Makefile --- old/txtorcon-19.0.0/Makefile 2019-01-16 06:04:26.000000000 +0100 +++ new/txtorcon-19.1.0/Makefile 2019-09-10 08:50:29.000000000 +0200 @@ -1,6 +1,6 @@ -.PHONY: test html counts coverage sdist clean install doc integration diagrams +.PHONY: test html counts coverage sdist clean install doc integration diagrams dist-hs default: test -VERSION = 19.0.0 +VERSION = 19.1.0 test: PYTHONPATH=. trial --reporter=text test @@ -103,6 +103,20 @@ dist-sigs: dist/txtorcon-${VERSION}-py2.py3-none-any.whl.asc dist/txtorcon-${VERSION}.tar.gz.asc +dist-hs: + cp dist/txtorcon-${VERSION}-py2.py3-none-any.whl ~/tools/dist/txtorcon3/git/docs/_build/html/ + cp dist/txtorcon-${VERSION}-py2.py3-none-any.whl.asc ~/tools/dist/txtorcon3/git/docs/_build/html/ + cp dist/txtorcon-${VERSION}-py2.py3-none-any.whl ~/tools/dist/txtorcon/git/docs/_build/html/ + cp dist/txtorcon-${VERSION}-py2.py3-none-any.whl.asc ~/tools/dist/txtorcon/git/docs/_build/html/ + cp dist/txtorcon-${VERSION}.tar.gz ~/tools/dist/txtorcon3/git/docs/_build/html/ + cp dist/txtorcon-${VERSION}.tar.gz.asc ~/tools/dist/txtorcon3/git/docs/_build/html/ + cp dist/txtorcon-${VERSION}.tar.gz ~/tools/dist/txtorcon/git/docs/_build/html/ + cp dist/txtorcon-${VERSION}.tar.gz.asc ~/tools/dist/txtorcon/git/docs/_build/html/ + +check-sigs: + gpg --verify dist/txtorcon-${VERSION}-py2.py3-none-any.whl.asc + gpg --verify dist/txtorcon-${VERSION}.tar.gz.asc + sdist: setup.py python setup.py check python setup.py sdist diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/PKG-INFO new/txtorcon-19.1.0/PKG-INFO --- old/txtorcon-19.0.0/PKG-INFO 2019-01-16 06:08:27.000000000 +0100 +++ new/txtorcon-19.1.0/PKG-INFO 2019-09-10 08:50:32.000000000 +0200 @@ -1,12 +1,7 @@ Metadata-Version: 2.1 Name: txtorcon -Version: 19.0.0 -Summary: - Twisted-based Tor controller client, with state-tracking and - configuration abstractions. - https://txtorcon.readthedocs.org - https://github.com/meejah/txtorcon - +Version: 19.1.0 +Summary: Twisted-based Tor controller client, with state-tracking and configuration abstractions. https://txtorcon.readthedocs.org https://github.com/meejah/txtorcon Home-page: https://github.com/meejah/txtorcon Author: meejah Author-email: mee...@meejah.ca @@ -62,12 +57,6 @@ `Automat <https://github.com/glyph/automat>`_, (and the `ipaddress <https://pypi.python.org/pypi/ipaddress>`_ backport for non Python 3) - .. caution:: - - Several large, new features have landed on master. If you're working - directly from master, note that some of these APIs may change before - the next release. - Ten Thousand Feet ----------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/README.rst new/txtorcon-19.1.0/README.rst --- old/txtorcon-19.0.0/README.rst 2018-12-11 17:15:06.000000000 +0100 +++ new/txtorcon-19.1.0/README.rst 2019-09-10 08:49:17.000000000 +0200 @@ -49,12 +49,6 @@ `Automat <https://github.com/glyph/automat>`_, (and the `ipaddress <https://pypi.python.org/pypi/ipaddress>`_ backport for non Python 3) -.. caution:: - - Several large, new features have landed on master. If you're working - directly from master, note that some of these APIs may change before - the next release. - Ten Thousand Feet ----------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/docs/guide.rst new/txtorcon-19.1.0/docs/guide.rst --- old/txtorcon-19.0.0/docs/guide.rst 2018-12-11 17:15:06.000000000 +0100 +++ new/txtorcon-19.1.0/docs/guide.rst 2019-09-10 08:13:40.000000000 +0200 @@ -127,7 +127,7 @@ Setting ``data_directory`` gives your Tor instance a place to cache its state information which includes the current "consensus" -document. If you don't set it, txtorcon creates a temporary directly +document. If you don't set it, txtorcon creates a temporary directory (which is deleted when this Tor instance exits). Startup time is drammatically improved if Tor already has a recent consensus, so when integrating with Tor by launching your own client it's highly @@ -285,6 +285,10 @@ interested in a single circuit, you can call :meth:`.Circuit.listen` directly on a ``Circuit`` instance. +You can instead use methods (which also function as decorators) such +as :meth:`.TorState.on_circuit_launched` or +:meth:`.TorState.on_stream_closed` to add listeners for single events. + The Tor relays are abstracted with :class:`.Router` instances. Again, these have read-only attributes for interesting information, e.g.: ``id_hex``, ``ip``, ``flags`` (a list of strings), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/docs/release-checklist.rst new/txtorcon-19.1.0/docs/release-checklist.rst --- old/txtorcon-19.0.0/docs/release-checklist.rst 2019-01-16 06:07:19.000000000 +0100 +++ new/txtorcon-19.1.0/docs/release-checklist.rst 2019-09-10 08:50:29.000000000 +0200 @@ -23,13 +23,13 @@ * update heading, date * on both signing-machine and build-machine shells: - * export VERSION=19.0.0 + * export VERSION=19.1.0 * (if on signing machine) "make dist" and "make dist-sigs" * creates: dist/txtorcon-${VERSION}.tar.gz.asc dist/txtorcon-${VERSION}-py2.py3-none-any.whl.asc - * add the signatures to "signatues/" + * add the signatures to "signatures/" cp dist/txtorcon-${VERSION}.tar.gz.asc dist/txtorcon-${VERSION}-py2.py3-none-any.whl.asc signatures/ * add ALL FOUR files to dist/ (OR fix twine commands) @@ -103,6 +103,9 @@ * copy dist/* files + signatures to hidden-service machine * copy them to the HTML build directory! (docs/_build/html/) + * make dist-hs + * make check-sigs + * git pull and build docs there * FIXME: why aren't all the dist files copied as part of doc build (only .tar.gz) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/docs/releases.rst new/txtorcon-19.1.0/docs/releases.rst --- old/txtorcon-19.0.0/docs/releases.rst 2019-01-16 06:07:08.000000000 +0100 +++ new/txtorcon-19.1.0/docs/releases.rst 2019-09-10 08:50:22.000000000 +0200 @@ -18,7 +18,26 @@ unreleased ---------- -`git master <https://github.com/meejah/txtorcon>`_ *will likely become v19.1.0* +`git master <https://github.com/meejah/txtorcon>`_ *will likely become v19.2.0* + + +v19.1.0 +------- + +September 10, 2019 + + * `txtorcon-19.1.0.tar.gz <http://timaq4ygg2iegci7.onion/txtorcon-19.1.0.tar.gz>`_ (`PyPI <https://pypi.python.org/pypi/txtorcon/19.1.0>`_ (:download:`local-sig </../signatues/txtorcon-19.1.0.tar.gz.asc>` or `github-sig <https://github.com/meejah/txtorcon/blob/master/signatues/txtorcon-19.1.0.tar.gz.asc?raw=true>`_) (`source <https://github.com/meejah/txtorcon/archive/v19.1.0.tar.gz>`_) + + * `TorControlProtocol.on_disconnect` is deprecated in favour of + :func:`TorControlProtocol.when_disconnected` + * introduce `non_anonymous_mode=` kwarg in :func:`txtorcon.launch` + enabling Tor options making Onion Services non-anonymous for the + server (but they use a single hop instead of three to the + Introduction Point so they're slightly faster). + * add an API to listen to individual circuit and stream events + (without subclassing anything). Can be used as decorators too. + See e.g. :func:`TorState.on_circuit_new` + * fixes to the CI setup to properly test Twisted versions v19.0.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/examples/monitor_compose.py new/txtorcon-19.1.0/examples/monitor_compose.py --- old/txtorcon-19.0.0/examples/monitor_compose.py 1970-01-01 01:00:00.000000000 +0100 +++ new/txtorcon-19.1.0/examples/monitor_compose.py 2019-04-23 06:53:35.000000000 +0200 @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +# Just listens for a few EVENTs from Tor (INFO NOTICE WARN ERR) and +# prints out the contents, so functions like a log monitor. + +from __future__ import print_function + +from twisted.internet import task, defer +from twisted.internet.endpoints import UNIXClientEndpoint +import txtorcon + + +@task.react +@defer.inlineCallbacks +def main(reactor): + ep = UNIXClientEndpoint(reactor, '/var/run/tor/control') + tor = yield txtorcon.connect(reactor, ep) + + def log(msg): + print(msg) + print("Connected to a Tor version", tor.protocol.version) + + state = yield tor.create_state() + + print(dir(state)) + @state.on_stream_new + def _(circ): + print("new stream: {}".format(circ)) + + yield defer.Deferred() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/examples/web_onion_service_nonanonymous.py new/txtorcon-19.1.0/examples/web_onion_service_nonanonymous.py --- old/txtorcon-19.0.0/examples/web_onion_service_nonanonymous.py 1970-01-01 01:00:00.000000000 +0100 +++ new/txtorcon-19.1.0/examples/web_onion_service_nonanonymous.py 2019-03-04 03:57:43.000000000 +0100 @@ -0,0 +1,56 @@ +#!/usr/bin/env python + +# This shows how to leverage the endpoints API to get a new hidden +# service up and running quickly. You can pass along this API to your +# users by accepting endpoint strings as per Twisted recommendations. +# +# http://twistedmatrix.com/documents/current/core/howto/endpoints.html#maximizing-the-return-on-your-endpoint-investment +# +# note that only the progress-updates needs the "import txtorcon" -- +# you do still need it installed so that Twisted finds the endpoint +# parser plugin but code without knowledge of txtorcon can still +# launch a Tor instance using it. cool! + +from __future__ import print_function +from twisted.internet import defer, task, endpoints +from twisted.web import server, resource + +import txtorcon +from txtorcon.util import default_control_port + + +class Simple(resource.Resource): + """ + A really simple Web site. + """ + isLeaf = True + + def render_GET(self, request): + print("serving request") + return b"<html>Hello, world! I'm a single-hop Onion Service!</html>" + + +@defer.inlineCallbacks +def main(reactor): + tor = yield txtorcon.launch( + reactor, + progress_updates=print, + non_anonymous_mode=True, + data_directory="./tor_data", + ) + print("{}".format(tor)) + hs = yield tor.create_filesystem_onion_service( + [(80, 8787)], + "./prop224_hs", + version=3, + ) + print("{}".format(hs)) + + ep = endpoints.TCP4ServerEndpoint(reactor, 8787, interface="localhost") + port = yield ep.listen(server.Site(Simple())) + print("Site listening: {}".format(hs.hostname)) + print("Private key:\n{}".format(hs.private_key)) + yield defer.Deferred() # wait forever + + +task.react(main) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/requirements.txt new/txtorcon-19.1.0/requirements.txt --- old/txtorcon-19.0.0/requirements.txt 2018-02-27 05:59:40.000000000 +0100 +++ new/txtorcon-19.1.0/requirements.txt 2019-09-10 06:54:13.000000000 +0200 @@ -6,3 +6,4 @@ zope.interface>=3.6.1 incremental automat +cryptography diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/setup.cfg new/txtorcon-19.1.0/setup.cfg --- old/txtorcon-19.0.0/setup.cfg 2019-01-16 06:08:27.000000000 +0100 +++ new/txtorcon-19.1.0/setup.cfg 2019-09-10 08:50:32.000000000 +0200 @@ -1,6 +1,3 @@ -[bdist_wheel] -universal = 1 - [egg_info] tag_build = tag_date = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/setup.py new/txtorcon-19.1.0/setup.py --- old/txtorcon-19.0.0/setup.py 2018-09-06 08:57:21.000000000 +0200 +++ new/txtorcon-19.1.0/setup.py 2019-09-10 08:50:22.000000000 +0200 @@ -25,6 +25,12 @@ https://txtorcon.readthedocs.org https://github.com/meejah/txtorcon ''' +# if there are any newlines in the short-description, there's no error +# .. but setuptools / pip / readme_renderere "or something" causes the +# 'descript' to be all the meta-data after 'summary', which fails to +# render. +# see: https://github.com/pypa/setuptools/issues/1390 +description = description.replace('\n', ' ') sphinx_rst_files = [x for x in listdir('docs') if x[-3:] == 'rst'] sphinx_docs = [join('docs', x) for x in sphinx_rst_files] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/test/test_controller.py new/txtorcon-19.1.0/test/test_controller.py --- old/txtorcon-19.0.0/test/test_controller.py 2018-12-11 17:15:06.000000000 +0100 +++ new/txtorcon-19.1.0/test/test_controller.py 2019-09-10 06:54:13.000000000 +0200 @@ -236,6 +236,20 @@ ) self.assertTrue("must exist" in str(ctx.exception)) + @defer.inlineCallbacks + def test_launch_tor_non_anonymous_and_socks(self): + reactor = FakeReactor(self, Mock(), None, [9050]) + with self.assertRaises(ValueError) as ctx: + yield launch( + reactor, + non_anonymous_mode=True, + socks_port=1234, + tor_binary="/bin/echo", + stdout=Mock(), + stderr=Mock(), + ) + self.assertIn("Cannot use SOCKS", str(ctx.exception)) + @patch('txtorcon.controller.find_tor_binary', return_value='/bin/echo') @defer.inlineCallbacks def test_launch_fails(self, ftb): @@ -290,6 +304,35 @@ tor = yield launch(reactor, _tor_config=config) self.assertTrue(isinstance(tor, Tor)) + @patch('txtorcon.controller.find_tor_binary', return_value='/bin/echo') + @patch('txtorcon.controller.TorProcessProtocol') + @defer.inlineCallbacks + def test_successful_launch_non_anonymous(self, tpp, ftb): + trans = FakeProcessTransport() + reactor = FakeReactor(self, trans, lambda p: None, [1, 2, 3]) + config = TorConfig() + + def boot(arg=None): + config.post_bootstrap.callback(config) + config.__dict__['bootstrap'] = Mock(side_effect=boot) + config.__dict__['attach_protocol'] = Mock(return_value=defer.succeed(None)) + + def foo(*args, **kw): + rtn = Mock() + rtn.post_bootstrap = defer.succeed(None) + rtn.when_connected = Mock(return_value=defer.succeed(rtn)) + return rtn + tpp.side_effect = foo + + tor = yield launch(reactor, _tor_config=config, non_anonymous_mode=True) + self.assertTrue(isinstance(tor, Tor)) + self.assertTrue(config.HiddenServiceNonAnonymousMode) + + with self.assertRaises(Exception): + yield tor.web_agent() + with self.assertRaises(Exception): + yield tor.dns_resolve('meejah.ca') + @defer.inlineCallbacks def test_quit(self): tor = Tor(Mock(), Mock()) @@ -1017,7 +1060,7 @@ print("Skipping; appears we don't have web support") return - resp = yield agent.request('GET', b'meejah.ca') + resp = yield agent.request(b'GET', b'meejah.ca') self.assertEqual(self.expected_response, resp) @defer.inlineCallbacks @@ -1031,7 +1074,7 @@ tor = Tor(reactor, proto, _tor_config=cfg) agent = tor.web_agent(pool=self.pool, socks_endpoint=socks_d) - resp = yield agent.request('GET', b'meejah.ca') + resp = yield agent.request(b'GET', b'meejah.ca') self.assertEqual(self.expected_response, resp) @defer.inlineCallbacks @@ -1046,7 +1089,7 @@ tor = Tor(reactor, proto, _tor_config=cfg) agent = tor.web_agent(pool=self.pool, socks_endpoint=socks) - resp = yield agent.request('GET', b'meejah.ca') + resp = yield agent.request(b'GET', b'meejah.ca') self.assertEqual(self.expected_response, resp) @defer.inlineCallbacks @@ -1059,7 +1102,7 @@ tor = Tor(reactor, proto, _tor_config=cfg) with self.assertRaises(ValueError) as ctx: agent = tor.web_agent(pool=self.pool, socks_endpoint=object()) - yield agent.request('GET', b'meejah.ca') + yield agent.request(B'GET', b'meejah.ca') self.assertTrue("'socks_endpoint' should be" in str(ctx.exception)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/test/test_torconfig.py new/txtorcon-19.1.0/test/test_torconfig.py --- old/txtorcon-19.0.0/test/test_torconfig.py 2018-07-18 07:53:09.000000000 +0200 +++ new/txtorcon-19.1.0/test/test_torconfig.py 2019-03-04 03:38:01.000000000 +0100 @@ -270,9 +270,9 @@ self.protocol.answers.append({'baz': 'auto'}) conf = TorConfig(self.protocol) - self.assertTrue(conf.foo is 0) - self.assertTrue(conf.bar is 1) - self.assertTrue(conf.baz is -1) + self.assertEqual(conf.foo, 0) + self.assertEqual(conf.bar, 1) + self.assertEqual(conf.baz, -1) def test_save_boolean_auto(self): self.protocol.answers.append( @@ -295,10 +295,10 @@ ('bar', 0), ('baz', 1), ('qux', 'auto')])) - self.assertTrue(conf.foo is 1) - self.assertTrue(conf.bar is 0) - self.assertTrue(conf.baz is 1) - self.assertTrue(conf.qux is -1) + self.assertEqual(conf.foo, 1) + self.assertEqual(conf.bar, 0) + self.assertEqual(conf.baz, 1) + self.assertEqual(conf.qux, -1) def test_save_invalid_boolean_auto(self): self.protocol.answers.append( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/test/test_torstate.py new/txtorcon-19.1.0/test/test_torstate.py --- old/txtorcon-19.0.0/test/test_torstate.py 2019-01-13 10:02:00.000000000 +0100 +++ new/txtorcon-19.1.0/test/test_torstate.py 2019-06-15 08:32:36.000000000 +0200 @@ -1534,3 +1534,146 @@ d.addErrback(check_reason) return d + + +class ComposibleListenerTests(unittest.TestCase): + + def setUp(self): + self.protocol = TorControlProtocol() + self.state = TorState(self.protocol) + + def test_circuit_new(self): + listener_calls = [] + + @self.state.on_circuit_new + def _(circ): + listener_calls.append(circ) + + c = self.state._maybe_create_circuit("42") + c.update(["42", "LAUNCHED"]) + + self.assertEqual(len(listener_calls), 1) + self.assertEqual(listener_calls[0].id, 42) + + def test_circuit_launched(self): + listener_calls = [] + + @self.state.on_circuit_launched + def _(circ): + listener_calls.append(circ) + + c = self.state._maybe_create_circuit("42") + c.update(["42", "LAUNCHED"]) + + self.assertEqual(len(listener_calls), 1) + self.assertEqual(listener_calls[0].id, 42) + + def test_circuit_extend(self): + listener_calls = [] + + @self.state.on_circuit_extend + def _(circ, router): + listener_calls.append(circ) + + c = self.state._maybe_create_circuit("42") + c.update(["42", "LAUNCHED"]) + c.update(["42", "BUILDING", "$deadbeef,$1ee7"]) + + # we get two calls because we've now extended to 2 relays + self.assertEqual(len(listener_calls), 2) + self.assertEqual(listener_calls[0].id, 42) + self.assertEqual(listener_calls[1].id, 42) + + def test_circuit_built(self): + listener_calls = [] + + @self.state.on_circuit_built + def _(circ): + listener_calls.append(circ) + + c = self.state._maybe_create_circuit("42") + c.update(["42", "LAUNCHED"]) + c.update(["42", "BUILT"]) + + self.assertEqual(len(listener_calls), 1) + self.assertEqual(listener_calls[0].id, 42) + + def test_circuit_closed(self): + listener_calls = [] + + @self.state.on_circuit_closed + def _(circ): + listener_calls.append(circ) + + c = self.state._maybe_create_circuit("42") + c.update(["42", "LAUNCHED"]) + c.update(["42", "CLOSED"]) + + self.assertEqual(len(listener_calls), 1) + self.assertEqual(listener_calls[0].id, 42) + + def test_circuit_failed(self): + listener_calls = [] + + @self.state.on_circuit_failed + def _(circ): + listener_calls.append(circ) + + c = self.state._maybe_create_circuit("42") + c.update(["42", "LAUNCHED"]) + c.update(["42", "FAILED"]) + + self.assertEqual(len(listener_calls), 1) + self.assertEqual(listener_calls[0].id, 42) + + def test_stream_events(self): + """ + one for the philosophers: is this 'one, but big' test better / + easier to read than the N circuit tests above? + """ + listener_calls = [] # list of 2-tuples + + @self.state.on_stream_new + def _(stream): + listener_calls.append(("new", stream.id)) + + @self.state.on_stream_succeeded + def _(stream): + listener_calls.append(("succeeded", stream.id)) + + @self.state.on_stream_attach + def _(stream, circ): + listener_calls.append(("attach", stream.id)) + + @self.state.on_stream_detach + def _(stream): + listener_calls.append(("detach", stream.id)) + + @self.state.on_stream_closed + def _(stream): + listener_calls.append(("closed", stream.id)) + + @self.state.on_stream_failed + def _(stream): + listener_calls.append(("failed", stream.id)) + + circ = self.state._maybe_create_circuit("42") + circ.update(["42", "LAUNCHED"]) + + self.state._stream_update("1234 NEW 0 meejah.ca:80") + self.state._stream_update("1234 SUCCEEDED 42") + self.state._stream_update("1234 DETACHED 0") + self.state._stream_update("1234 CLOSED 0") + self.state._stream_update("1234 FAILED 0") + + self.assertEqual( + listener_calls, + [ + ("new", 1234), + ("succeeded", 1234), + ("attach", 1234), + ("detach", 1234), + ("closed", 1234), + ("failed", 1234), + ] + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/test/test_util.py new/txtorcon-19.1.0/test/test_util.py --- old/txtorcon-19.0.0/test/test_util.py 2018-07-17 21:02:42.000000000 +0200 +++ new/txtorcon-19.1.0/test/test_util.py 2019-03-04 03:38:01.000000000 +0100 @@ -14,7 +14,6 @@ from txtorcon.util import process_from_address from txtorcon.util import delete_file_or_tree from txtorcon.util import find_keywords -from txtorcon.util import ip_from_int from txtorcon.util import find_tor_binary from txtorcon.util import maybe_ip_addr from txtorcon.util import unescape_quoted_string @@ -44,12 +43,6 @@ return None -class TestIPFromInt(unittest.TestCase): - - def test_cast(self): - self.assertEqual(ip_from_int(0x7f000001), '127.0.0.1') - - class TestGeoIpDatabaseLoading(unittest.TestCase): def test_bad_geoip_path(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/txtorcon/_metadata.py new/txtorcon-19.1.0/txtorcon/_metadata.py --- old/txtorcon-19.0.0/txtorcon/_metadata.py 2019-01-16 06:04:37.000000000 +0100 +++ new/txtorcon-19.1.0/txtorcon/_metadata.py 2019-09-10 08:50:22.000000000 +0200 @@ -1,4 +1,4 @@ -__version__ = '19.0.0' +__version__ = '19.1.0' __author__ = 'meejah' __contact__ = 'mee...@meejah.ca' __url__ = 'https://github.com/meejah/txtorcon' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/txtorcon/circuit.py new/txtorcon-19.1.0/txtorcon/circuit.py --- old/txtorcon-19.0.0/txtorcon/circuit.py 2018-09-27 03:34:37.000000000 +0200 +++ new/txtorcon-19.1.0/txtorcon/circuit.py 2019-06-15 08:32:36.000000000 +0200 @@ -494,7 +494,7 @@ rendevouz point not in the current consensus. """ - oldpath = self.path + oldpath_len = len(self.path) self.path = [] for p in path: if p[0] != '$': @@ -506,10 +506,10 @@ self.path.append(router) # if the path grew, notify listeners - if len(self.path) > len(oldpath): + if len(self.path) > oldpath_len: for x in self.listeners: x.circuit_extend(self, router) - oldpath = self.path + oldpath_len = len(self.path) def __str__(self): path = ' '.join([x.ip for x in self.path]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/txtorcon/controller.py new/txtorcon-19.1.0/txtorcon/controller.py --- old/txtorcon-19.0.0/txtorcon/controller.py 2018-12-11 17:15:06.000000000 +0100 +++ new/txtorcon-19.1.0/txtorcon/controller.py 2019-03-11 01:37:39.000000000 +0100 @@ -55,6 +55,7 @@ control_port=None, data_directory=None, socks_port=None, + non_anonymous_mode=None, stdout=None, stderr=None, timeout=None, @@ -110,6 +111,16 @@ faster. If ``None`` (the default), we create a tempdir for this **and delete it on exit**. It is recommended you pass something here. + :param non_anonymous_mode: sets the Tor options + `HiddenServiceSingleHopMode` and + `HiddenServiceNonAnonymousMode` to 1 and un-sets any + `SOCKSPort` config, thus putting this Tor client into + "non-anonymous mode" which allows starting so-called Single + Onion services -- which use single-hop circuits to rendezvous + points. See WARNINGs in Tor manual! Also you need Tor + `0.3.4.1` or later (e.g. any `0.3.5.*` or newer) for this to + work properly. + :param stdout: a file-like object to which we write anything that Tor prints on stdout (just needs to support write()). @@ -221,9 +232,18 @@ except KeyError: socks_port = None - if socks_port is None: - socks_port = yield available_tcp_port(reactor) - config.SOCKSPort = socks_port + if non_anonymous_mode: + if socks_port is not None: + raise ValueError( + "Cannot use SOCKS options with non_anonymous_mode=True" + ) + config.HiddenServiceNonAnonymousMode = 1 + config.HiddenServiceSingleHopMode = 1 + config.SOCKSPort = 0 + else: + if socks_port is None: + socks_port = yield available_tcp_port(reactor) + config.SOCKSPort = socks_port try: our_user = user or config.User @@ -350,6 +370,7 @@ config.protocol, _tor_config=config, _process_proto=process_protocol, + _non_anonymous=True if non_anonymous_mode else False, ) ) @@ -474,7 +495,7 @@ print(port.getHost()) """ - def __init__(self, reactor, control_protocol, _tor_config=None, _process_proto=None): + def __init__(self, reactor, control_protocol, _tor_config=None, _process_proto=None, _non_anonymous=None): """ don't instantiate this class yourself -- instead use the factory methods :func:`txtorcon.launch` or :func:`txtorcon.connect` @@ -487,6 +508,8 @@ # cache our preferred socks port (please use # self._default_socks_endpoint() to get one) self._socks_endpoint = None + # True if we've turned on non-anonymous mode / Onion services + self._non_anonymous = _non_anonymous @inlineCallbacks def quit(self): @@ -552,6 +575,10 @@ :param pool: passed on to the Agent (as ``pool=``) """ + if self._non_anonymous: + raise Exception( + "Cannot use web_agent when in non_anonymous mode" + ) # local import since not all platforms have this from txtorcon import web @@ -932,6 +959,10 @@ (which might mean setting one up in our attacked Tor if it doesn't have one) """ + if self._non_anonymous: + raise Exception( + "Cannot use SOCKS when in non_anonymous mode" + ) if self._socks_endpoint is None: self._socks_endpoint = yield _create_socks_endpoint(self._reactor, self._protocol) returnValue(self._socks_endpoint) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/txtorcon/onion.py new/txtorcon-19.1.0/txtorcon/onion.py --- old/txtorcon-19.0.0/txtorcon/onion.py 2018-12-11 17:15:06.000000000 +0100 +++ new/txtorcon-19.1.0/txtorcon/onion.py 2019-03-11 01:37:39.000000000 +0100 @@ -170,7 +170,11 @@ @staticmethod @defer.inlineCallbacks - def create(reactor, config, hsdir, ports, version=3, group_readable=False, progress=None, await_all_uploads=None): + def create(reactor, config, hsdir, ports, + version=3, + group_readable=False, + progress=None, + await_all_uploads=None): """ returns a new FilesystemOnionService after adding it to the provided config and ensuring at least one of its descriptors @@ -1076,7 +1080,12 @@ @staticmethod @defer.inlineCallbacks - def create(reactor, config, hsdir, ports, auth=None, version=3, group_readable=False, progress=None, await_all_uploads=None): + def create(reactor, config, hsdir, ports, + auth=None, + version=3, + group_readable=False, + progress=None, + await_all_uploads=None): """ returns a new FilesystemAuthenticatedOnionService after adding it to the provided config and ensureing at least one of its diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/txtorcon/torcontrolprotocol.py new/txtorcon-19.1.0/txtorcon/torcontrolprotocol.py --- old/txtorcon-19.0.0/txtorcon/torcontrolprotocol.py 2019-01-13 10:02:00.000000000 +0100 +++ new/txtorcon-19.1.0/txtorcon/torcontrolprotocol.py 2019-03-04 03:38:01.000000000 +0100 @@ -8,6 +8,7 @@ import re import base64 from binascii import b2a_hex, hexlify +from warnings import warn from twisted.python import log from twisted.internet import defer @@ -23,7 +24,8 @@ from txtorcon.interface import ITorControlProtocol from .spaghetti import FSM, State, Transition -from .util import maybe_coroutine, SingleObserver +from .util import maybe_coroutine +from .util import SingleObserver DEFAULT_VALUE = 'DEFAULT' @@ -263,12 +265,18 @@ self.valid_signals = [] """A list of all valid signals we accept from Tor""" - # XXX bad practice; this should be like an on_disconnct() - # method that returns a new Deferred each time... + # NOTE: bad practice (and now deprecated) -- use + # when_disconnected() instead self.on_disconnect = defer.Deferred() """ - This Deferred is triggered when the connection is closed. If - there was an error, the errback is called instead. + This Deferred is triggered when the connection is closed. If there + was an error, the errback is called instead. (Deprecated: use + :func:`when_disconnected` instead) + """ + + self._when_disconnected = SingleObserver() + """ + Internal use. A :class:`SingleObserver` for when_disconnected() """ self._when_disconnected = SingleObserver() @@ -694,13 +702,17 @@ # returned a new Deferred to each caller..(we're checking if # this Deferred has any callbacks because if it doesn't we'll # generate an "Unhandled error in Deferred") - # XXX (deprecate this) - if not self.on_disconnect.called and self.on_disconnect.callbacks: + if self.on_disconnect and not self.on_disconnect.called and self.on_disconnect.callbacks: + warn( + 'TorControlProtocol.on_disconnect is deprecated; use .when_disconnected() instead', + DeprecationWarning, + ) if reason.check(ConnectionDone): self.on_disconnect.callback(self) else: self.on_disconnect.errback(reason) self.on_disconnect = None + self._when_disconnected.fire(self) outstanding = [self.command] + self.commands if self.command else self.commands for d, cmd, cmd_arg in outstanding: if not d.called: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/txtorcon/torstate.py new/txtorcon-19.1.0/txtorcon/torstate.py --- old/txtorcon-19.0.0/txtorcon/torstate.py 2019-01-16 06:03:06.000000000 +0100 +++ new/txtorcon-19.1.0/txtorcon/torstate.py 2019-06-15 08:32:36.000000000 +0200 @@ -32,6 +32,8 @@ from txtorcon.interface import ICircuitContainer from txtorcon.interface import IStreamListener from txtorcon.interface import IStreamAttacher +from txtorcon.interface import StreamListenerMixin +from txtorcon.interface import CircuitListenerMixin from ._microdesc_parser import MicrodescriptorParser from .router import hexIdFromHash from .util import maybe_coroutine @@ -516,6 +518,66 @@ 'CLOSECIRCUIT %s%s' % (circid, flags) ) + def on_circuit_new(self, callback): + """ + :param callback: will be called (with 'circuit' instance) when a + CIRC NEW event happens + """ + listener = CircuitListenerMixin() + listener.circuit_new = callback + self.add_circuit_listener(listener) + return callback # so we can be used as a listener + + def on_circuit_launched(self, callback): + """ + :param callback: will be called (with 'circuit' instance) when a + CIRC LAUNCHED event happens + """ + listener = CircuitListenerMixin() + listener.circuit_launched = callback + self.add_circuit_listener(listener) + return callback # so we can be used as a listener + + def on_circuit_extend(self, callback): + """ + :param callback: will be called (with 'circuit' and 'router' + instances) when a CIRC EXTENDED event happens + """ + listener = CircuitListenerMixin() + listener.circuit_extend = callback + self.add_circuit_listener(listener) + return callback # so we can be used as a listener + + def on_circuit_built(self, callback): + """ + :param callback: will be called (with 'circuit' instance) when a + CIRC BUILT event happens + """ + listener = CircuitListenerMixin() + listener.circuit_built = callback + self.add_circuit_listener(listener) + return callback # so we can be used as a listener + + def on_circuit_closed(self, callback): + """ + :param callback: will be called (with 'circuit' instance, and + arbitrary kwargs) when a CIRC CLOSED event happens + """ + listener = CircuitListenerMixin() + listener.circuit_closed = callback + self.add_circuit_listener(listener) + return callback # so we can be used as a listener + + def on_circuit_failed(self, callback): + """ + :param callback: will be called (with 'circuit' instance, and + arbitrary kwargs) when a CIRC FAILED event happens + """ + listener = CircuitListenerMixin() + listener.circuit_failed = callback + self.add_circuit_listener(listener) + return callback # so we can be used as a listener + def add_circuit_listener(self, icircuitlistener): """ Adds a new instance of :class:`txtorcon.interface.ICircuitListener` which @@ -526,6 +588,66 @@ circ.listen(listen) self.circuit_listeners.append(listen) + def on_stream_new(self, callback): + """ + :param callback: will be called (with 'stream' instance) when a + STREAM NEW event happens. + """ + listener = StreamListenerMixin() + listener.stream_new = callback + self.add_stream_listener(listener) + return callback # so we can be used as a listener + + def on_stream_succeeded(self, callback): + """ + :param callback: will be called (with 'stream' instance) when a + STREAM SUCCEEDED event happens. + """ + listener = StreamListenerMixin() + listener.stream_succeeded = callback + self.add_stream_listener(listener) + return callback # so we can be used as a listener + + def on_stream_attach(self, callback): + """ + :param callback: will be called (with 'stream' and 'circuit' + instances) when a stream is attached to a circuit + """ + listener = StreamListenerMixin() + listener.stream_attach = callback + self.add_stream_listener(listener) + return callback # so we can be used as a listener + + def on_stream_detach(self, callback): + """ + :param callback: will be called (with 'stream' instance and + arbitrary kwargs) when a STREAM DETACHED happens + """ + listener = StreamListenerMixin() + listener.stream_detach = callback + self.add_stream_listener(listener) + return callback # so we can be used as a listener + + def on_stream_closed(self, callback): + """ + :param callback: will be called (with 'stream' instance and + arbitrary kwargs) when a STREAM CLOSED event happens. + """ + listener = StreamListenerMixin() + listener.stream_closed = callback + self.add_stream_listener(listener) + return callback # so we can be used as a listener + + def on_stream_failed(self, callback): + """ + :param callback: will be called (with 'stream' instance and + arbitrary kwargs) when a STREAM FAILED event happens. + """ + listener = StreamListenerMixin() + listener.stream_failed = callback + self.add_stream_listener(listener) + return callback # so we can be used as a listener + def add_stream_listener(self, istreamlistener): """ Adds a new instance of :class:`txtorcon.interface.IStreamListener` which diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/txtorcon/util.py new/txtorcon-19.1.0/txtorcon/util.py --- old/txtorcon-19.0.0/txtorcon/util.py 2018-10-25 17:47:35.000000000 +0200 +++ new/txtorcon-19.1.0/txtorcon/util.py 2019-03-04 03:38:01.000000000 +0100 @@ -9,10 +9,8 @@ import hmac import hashlib import shutil -import socket import subprocess import ipaddress -import struct import re import six @@ -192,11 +190,6 @@ shutil.rmtree(f, ignore_errors=True) -def ip_from_int(ip): - """ Convert long int back to dotted quad string """ - return socket.inet_ntoa(struct.pack('>I', ip)) - - def process_from_address(addr, port, torstate=None): """ Determines the PID from the address/port provided by using lsof diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/txtorcon.egg-info/PKG-INFO new/txtorcon-19.1.0/txtorcon.egg-info/PKG-INFO --- old/txtorcon-19.0.0/txtorcon.egg-info/PKG-INFO 2019-01-16 06:08:27.000000000 +0100 +++ new/txtorcon-19.1.0/txtorcon.egg-info/PKG-INFO 2019-09-10 08:50:32.000000000 +0200 @@ -1,12 +1,7 @@ Metadata-Version: 2.1 Name: txtorcon -Version: 19.0.0 -Summary: - Twisted-based Tor controller client, with state-tracking and - configuration abstractions. - https://txtorcon.readthedocs.org - https://github.com/meejah/txtorcon - +Version: 19.1.0 +Summary: Twisted-based Tor controller client, with state-tracking and configuration abstractions. https://txtorcon.readthedocs.org https://github.com/meejah/txtorcon Home-page: https://github.com/meejah/txtorcon Author: meejah Author-email: mee...@meejah.ca @@ -62,12 +57,6 @@ `Automat <https://github.com/glyph/automat>`_, (and the `ipaddress <https://pypi.python.org/pypi/ipaddress>`_ backport for non Python 3) - .. caution:: - - Several large, new features have landed on master. If you're working - directly from master, note that some of these APIs may change before - the next release. - Ten Thousand Feet ----------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/txtorcon.egg-info/SOURCES.txt new/txtorcon-19.1.0/txtorcon.egg-info/SOURCES.txt --- old/txtorcon-19.0.0/txtorcon.egg-info/SOURCES.txt 2019-01-16 06:08:27.000000000 +0100 +++ new/txtorcon-19.1.0/txtorcon.egg-info/SOURCES.txt 2019-09-10 08:50:32.000000000 +0200 @@ -7,7 +7,6 @@ dev-requirements.txt meejah.asc requirements.txt -setup.cfg setup.py docs/Makefile docs/apilinks_sphinxext.py @@ -60,6 +59,7 @@ examples/launch_tor_with_simplehttpd.py examples/minimal_endpoint.py examples/monitor.py +examples/monitor_compose.py examples/readme.py examples/readme3.py examples/stem_relay_descriptor.py @@ -80,6 +80,7 @@ examples/web_onion_service_ephemeral_nonanon.py examples/web_onion_service_ephemeral_unix.py examples/web_onion_service_filesystem.py +examples/web_onion_service_nonanonymous.py examples/web_onion_service_prop224.py examples/web_onion_service_prop224_endpoints.py examples/web_onion_service_prop224_endpoints_ephemeral.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/txtorcon-19.0.0/txtorcon.egg-info/requires.txt new/txtorcon-19.1.0/txtorcon.egg-info/requires.txt --- old/txtorcon-19.0.0/txtorcon.egg-info/requires.txt 2019-01-16 06:08:27.000000000 +0100 +++ new/txtorcon-19.1.0/txtorcon.egg-info/requires.txt 2019-09-10 08:50:32.000000000 +0200 @@ -2,6 +2,7 @@ zope.interface>=3.6.1 incremental automat +cryptography [:python_version < "3"] ipaddress>=1.0.16