2016-06-03 22:17:47 +00:00
|
|
|
from __future__ import print_function
|
|
|
|
|
|
|
|
import os
|
|
|
|
import time
|
|
|
|
start = time.time()
|
|
|
|
import traceback
|
|
|
|
from textwrap import fill, dedent
|
|
|
|
from sys import stdout, stderr
|
|
|
|
from . import public_relay
|
|
|
|
from .. import __version__
|
|
|
|
from ..timing import DebugTiming
|
|
|
|
from ..errors import WrongPasswordError, WelcomeError, KeyFormatError
|
|
|
|
from twisted.internet.defer import inlineCallbacks, maybeDeferred
|
|
|
|
from twisted.internet.task import react
|
|
|
|
|
|
|
|
import click
|
|
|
|
top_import_finish = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
class Config(object):
|
|
|
|
"""
|
|
|
|
Union of config options that we pass down to (sub) commands.
|
|
|
|
"""
|
|
|
|
def __init__(self):
|
2016-07-15 04:37:35 +00:00
|
|
|
# This only holds attributes which are *not* set by CLI arguments.
|
|
|
|
# Everything else comes from Click decorators, so we can be sure
|
|
|
|
# we're exercising the defaults.
|
2016-06-03 22:17:47 +00:00
|
|
|
self.timing = DebugTiming()
|
|
|
|
self.cwd = os.getcwd()
|
|
|
|
self.stdout = stdout
|
|
|
|
self.stderr = stderr
|
|
|
|
|
|
|
|
|
|
|
|
ALIASES = {
|
|
|
|
"tx": "send",
|
|
|
|
"rx": "receive",
|
|
|
|
}
|
|
|
|
class AliasedGroup(click.Group):
|
|
|
|
def get_command(self, ctx, cmd_name):
|
|
|
|
cmd_name = ALIASES.get(cmd_name, cmd_name)
|
|
|
|
return click.Group.get_command(self, ctx, cmd_name)
|
|
|
|
|
|
|
|
|
|
|
|
# top-level command ("wormhole ...")
|
|
|
|
@click.group(cls=AliasedGroup)
|
|
|
|
@click.option(
|
|
|
|
"--relay-url", default=public_relay.RENDEZVOUS_RELAY,
|
|
|
|
metavar="URL",
|
|
|
|
help="rendezvous relay to use",
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"--transit-helper", default=public_relay.TRANSIT_RELAY,
|
|
|
|
metavar="tcp:HOST:PORT",
|
|
|
|
help="transit relay to use",
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"-c", "--code-length", default=2,
|
|
|
|
metavar="NUMWORDS",
|
|
|
|
help="length of code (in bytes/words)",
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"-v", "--verify", is_flag=True, default=False,
|
|
|
|
help="display (and wait for acceptance of) verification string",
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"--hide-progress", is_flag=True, default=False,
|
|
|
|
help="supress progress-bar display",
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"--dump-timing", type=type(u""), # TODO: hide from --help output
|
|
|
|
default=None,
|
|
|
|
metavar="FILE.json",
|
|
|
|
help="(debug) write timing data to file",
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"--no-listen", is_flag=True, default=False,
|
|
|
|
help="(debug) don't open a listening socket for Transit",
|
|
|
|
)
|
|
|
|
@click.option(
|
2016-06-27 01:14:07 +00:00
|
|
|
"--tor", is_flag=True, default=False,
|
2016-06-03 22:17:47 +00:00
|
|
|
help="use Tor when connecting",
|
|
|
|
)
|
|
|
|
@click.version_option(
|
|
|
|
message="magic-wormhole %(version)s",
|
|
|
|
version=__version__,
|
|
|
|
)
|
|
|
|
@click.pass_context
|
2016-06-27 01:14:07 +00:00
|
|
|
def wormhole(context, tor, no_listen, dump_timing, hide_progress,
|
2016-06-03 22:17:47 +00:00
|
|
|
verify, code_length, transit_helper, relay_url):
|
|
|
|
"""
|
|
|
|
Create a Magic Wormhole and communicate through it.
|
|
|
|
|
|
|
|
Wormholes are created by speaking the same magic CODE in two
|
|
|
|
different places at the same time. Wormholes are secure against
|
|
|
|
anyone who doesn't use the same code.
|
|
|
|
"""
|
2016-06-27 01:14:07 +00:00
|
|
|
context.obj = cfg = Config()
|
|
|
|
cfg.tor = tor
|
|
|
|
cfg.listen = not no_listen
|
2016-06-03 22:17:47 +00:00
|
|
|
cfg.relay_url = relay_url
|
|
|
|
cfg.transit_helper = transit_helper
|
|
|
|
cfg.code_length = code_length
|
|
|
|
cfg.verify = verify
|
|
|
|
cfg.hide_progress = hide_progress
|
|
|
|
cfg.dump_timing = dump_timing
|
|
|
|
|
|
|
|
|
|
|
|
@inlineCallbacks
|
|
|
|
def _dispatch_command(reactor, cfg, command):
|
|
|
|
"""
|
2016-06-22 07:29:13 +00:00
|
|
|
Internal helper. This calls the given command (a no-argument
|
2016-06-03 22:17:47 +00:00
|
|
|
callable) with the Config instance in cfg and interprets any
|
|
|
|
errors for the user.
|
|
|
|
"""
|
|
|
|
cfg.timing.add("command dispatch")
|
|
|
|
cfg.timing.add("import", when=start, which="top").finish(when=top_import_finish)
|
|
|
|
|
|
|
|
try:
|
|
|
|
yield maybeDeferred(command)
|
|
|
|
except WrongPasswordError as e:
|
|
|
|
msg = fill("ERROR: " + dedent(e.__doc__))
|
|
|
|
print(msg, file=stderr)
|
|
|
|
except WelcomeError as e:
|
|
|
|
msg = fill("ERROR: " + dedent(e.__doc__))
|
|
|
|
print(msg, file=stderr)
|
|
|
|
print(file=stderr)
|
|
|
|
print(str(e), file=stderr)
|
|
|
|
except KeyFormatError as e:
|
|
|
|
msg = fill("ERROR: " + dedent(e.__doc__))
|
|
|
|
print(msg, file=stderr)
|
|
|
|
except Exception as e:
|
|
|
|
traceback.print_exc()
|
|
|
|
print("ERROR:", e, file=stderr)
|
|
|
|
raise SystemExit(1)
|
|
|
|
|
|
|
|
cfg.timing.add("exit")
|
|
|
|
if cfg.dump_timing:
|
|
|
|
cfg.timing.write(cfg.dump_timing, stderr)
|
2016-06-20 23:06:15 +00:00
|
|
|
|
2016-06-03 22:17:47 +00:00
|
|
|
|
|
|
|
# wormhole send (or "wormhole tx")
|
|
|
|
@wormhole.command()
|
|
|
|
@click.option(
|
2016-06-20 23:06:15 +00:00
|
|
|
"-0", "zeromode", default=False, is_flag=True,
|
|
|
|
help="enable no-code anything-goes mode",
|
2016-06-03 22:17:47 +00:00
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"--code", metavar="CODE",
|
|
|
|
help="human-generated code phrase",
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"--text", default=None, metavar="MESSAGE",
|
|
|
|
help="text message to send, instead of a file. Use '-' to read from stdin.",
|
|
|
|
)
|
2016-06-27 01:14:07 +00:00
|
|
|
@click.argument("what", required=False)
|
2016-06-03 22:17:47 +00:00
|
|
|
@click.pass_obj
|
|
|
|
def send(cfg, what, text, code, zeromode):
|
|
|
|
"""Send a text message, file, or directory"""
|
|
|
|
with cfg.timing.add("import", which="cmd_send"):
|
|
|
|
from . import cmd_send
|
|
|
|
cfg.what = what
|
|
|
|
cfg.text = text
|
|
|
|
cfg.zeromode = zeromode
|
|
|
|
cfg.code = code
|
|
|
|
|
2016-07-15 04:22:01 +00:00
|
|
|
return go(cmd_send.send, cfg)
|
|
|
|
|
|
|
|
# this intermediate function can be mocked by tests that need to build a
|
|
|
|
# Config object
|
|
|
|
def go(f, cfg):
|
2016-06-22 20:15:07 +00:00
|
|
|
# note: react() does not return
|
2016-07-15 04:22:01 +00:00
|
|
|
return react(_dispatch_command, (cfg, lambda: f(cfg)))
|
2016-06-03 22:17:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
# wormhole receive (or "wormhole rx")
|
|
|
|
@wormhole.command()
|
2016-06-20 23:06:15 +00:00
|
|
|
@click.option(
|
|
|
|
"-0", "zeromode", default=False, is_flag=True,
|
|
|
|
help="enable no-code anything-goes mode",
|
|
|
|
)
|
2016-06-03 22:17:47 +00:00
|
|
|
@click.option(
|
|
|
|
"--only-text", "-t", is_flag=True,
|
|
|
|
help="refuse file transfers, only accept text transfers",
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"--accept-file", is_flag=True,
|
|
|
|
help="accept file transfer without asking for confirmation",
|
|
|
|
)
|
|
|
|
@click.option(
|
|
|
|
"--output-file", "-o",
|
|
|
|
metavar="FILENAME|DIRNAME",
|
|
|
|
help=("The file or directory to create, overriding the name suggested"
|
|
|
|
" by the sender."),
|
|
|
|
)
|
|
|
|
@click.argument(
|
|
|
|
"code", nargs=-1, default=None,
|
|
|
|
# help=("The magic-wormhole code, from the sender. If omitted, the"
|
|
|
|
# " program will ask for it, using tab-completion."),
|
|
|
|
)
|
|
|
|
@click.pass_obj
|
|
|
|
def receive(cfg, code, zeromode, output_file, accept_file, only_text):
|
|
|
|
"""
|
|
|
|
Receive a text message, file, or directory (from 'wormhole send')
|
|
|
|
"""
|
|
|
|
with cfg.timing.add("import", which="cmd_receive"):
|
|
|
|
from . import cmd_receive
|
|
|
|
cfg.zeromode = zeromode
|
|
|
|
cfg.output_file = output_file
|
|
|
|
cfg.accept_file = accept_file
|
|
|
|
cfg.only_text = only_text
|
|
|
|
if len(code) == 1:
|
|
|
|
cfg.code = code[0]
|
|
|
|
elif len(code) > 1:
|
|
|
|
print(
|
|
|
|
"Pass either no code or just one code; you passed"
|
|
|
|
" {}: {}".format(len(code), ', '.join(code))
|
|
|
|
)
|
|
|
|
raise SystemExit(1)
|
|
|
|
else:
|
|
|
|
cfg.code = None
|
|
|
|
|
2016-07-15 04:22:01 +00:00
|
|
|
return go(cmd_receive.receive, cfg)
|