magic-wormhole/src/wormhole/xfer_util.py
Brian Warner 47007273ec rewrite Tor support (py2 only)
The new TorManager adds --launch-tor and --tor-control-port= arguments
(requiring the user to explicitly request a new Tor process, if that's what
they want). The default (when --tor is enabled) looks for a control port in
the usual places (/var/run/tor/control, localhost:9051, localhost:9151), then
falls back to hoping there's a SOCKS port in the usual
place (localhost:9050). (closes #64)

The ssh utilities should now accept the same tor arguments as ordinary
send/receive commands. There are now full tests for TorManager, and basic
tests for how send/receive use it. (closes #97)

Note that Tor is only supported on python2.7 for now, since txsocksx (and
therefore txtorcon) doesn't work on py3. You need to do "pip install
magic-wormhole[tor]" to get Tor support, and that will get you an inscrutable
error on py3 (referencing vcversioner, "install_requires must be a string or
list of strings", and "int object not iterable").

To run tests, you must install with the [dev] extra (to get "mock" and other
libraries). Our setup.py only includes "txtorcon" in the [dev] extra when on
py2, not on py3. Unit tests tolerate the lack of txtorcon (they mock out
everything txtorcon would provide), so they should provide the same coverage
on both py2 and py3.
2017-01-15 22:39:03 -05:00

128 lines
4.1 KiB
Python

import json
from twisted.internet.defer import inlineCallbacks, returnValue
from .wormhole import wormhole
from .tor_manager import TorManager
from .errors import NoTorError
@inlineCallbacks
def receive(reactor, appid, relay_url, code,
use_tor=False, launch_tor=False, tor_control_port=None,
on_code=None):
"""
This is a convenience API which returns a Deferred that callbacks
with a single chunk of data from another wormhole (and then closes
the wormhole). Under the hood, it's just using an instance
returned from :func:`wormhole.wormhole`. This is similar to the
`wormhole receive` command.
:param unicode appid: our application ID
:param unicode relay_url: the relay URL to use
:param unicode code: a pre-existing code to use, or None
:param bool use_tor: True if we should use Tor, False to not use it (None for default)
:param on_code: if not None, this is called when we have a code (even if you passed in one explicitly)
:type on_code: single-argument callable
"""
tm = None
if use_tor:
tm = TorManager(reactor, launch_tor, tor_control_port)
# For now, block everything until Tor has started. Soon: launch
# tor in parallel with everything else, make sure the TorManager
# can lazy-provide an endpoint, and overlap the startup process
# with the user handing off the wormhole code
if not tm.tor_available():
raise NoTorError()
yield tm.start()
wh = wormhole(appid, relay_url, reactor, tor_manager=tm)
if code is None:
code = yield wh.get_code()
else:
wh.set_code(code)
# we'll call this no matter what, even if you passed in a code --
# maybe it should be only in the 'if' block above?
if on_code:
on_code(code)
data = yield wh.get()
data = json.loads(data.decode("utf-8"))
offer = data.get('offer', None)
if not offer:
raise Exception(
"Do not understand response: {}".format(data)
)
msg = None
if 'message' in offer:
msg = offer['message']
wh.send(json.dumps({"answer": {"message_ack": "ok"}}).encode("utf-8"))
else:
raise Exception(
"Unknown offer type: {}".format(offer.keys())
)
yield wh.close()
returnValue(msg)
@inlineCallbacks
def send(reactor, appid, relay_url, data, code,
use_tor=False, launch_tor=False, tor_control_port=None,
on_code=None):
"""
This is a convenience API which returns a Deferred that callbacks
after a single chunk of data has been sent to another
wormhole. Under the hood, it's just using an instance returned
from :func:`wormhole.wormhole`. This is similar to the `wormhole
send` command.
:param unicode appid: the application ID
:param unicode relay_url: the relay URL to use
:param unicode code: a pre-existing code to use, or None
:param bool use_tor: True if we should use Tor, False to not use it (None for default)
:param on_code: if not None, this is called when we have a code (even if you passed in one explicitly)
:type on_code: single-argument callable
"""
tm = None
if use_tor:
tm = TorManager(reactor, launch_tor, tor_control_port)
# For now, block everything until Tor has started. Soon: launch
# tor in parallel with everything else, make sure the TorManager
# can lazy-provide an endpoint, and overlap the startup process
# with the user handing off the wormhole code
if not tm.tor_available():
raise NoTorError()
yield tm.start()
wh = wormhole(appid, relay_url, reactor, tor_manager=tm)
if code is None:
code = yield wh.get_code()
else:
wh.set_code(code)
if on_code:
on_code(code)
wh.send(
json.dumps({
"offer": {
"message": data
}
}).encode("utf-8")
)
data = yield wh.get()
data = json.loads(data.decode("utf-8"))
answer = data.get('answer', None)
yield wh.close()
if answer:
returnValue(None)
else:
raise Exception(
"Unknown answer: {}".format(data)
)