2016-08-14 23:14:29 +00:00
|
|
|
from __future__ import print_function
|
|
|
|
|
2016-08-15 01:57:00 +00:00
|
|
|
import os
|
|
|
|
from os.path import expanduser, exists, join
|
2016-08-14 23:14:29 +00:00
|
|
|
from twisted.internet.defer import inlineCallbacks
|
|
|
|
from twisted.internet import reactor
|
2016-08-15 01:57:00 +00:00
|
|
|
import click
|
2016-08-14 23:14:29 +00:00
|
|
|
|
|
|
|
from .. import xfer_util
|
|
|
|
|
2016-08-16 00:01:39 +00:00
|
|
|
class PubkeyError(Exception):
|
|
|
|
pass
|
2016-08-14 23:14:29 +00:00
|
|
|
|
2016-08-15 01:57:00 +00:00
|
|
|
def find_public_key(hint=None):
|
2016-08-14 23:14:29 +00:00
|
|
|
"""
|
|
|
|
This looks for an appropriate SSH key to send, possibly querying
|
2016-08-15 01:57:00 +00:00
|
|
|
the user in the meantime. DO NOT CALL after reactor.run as this
|
|
|
|
(possibly) does blocking stuff like asking the user questions (via
|
|
|
|
click.prompt())
|
2016-08-14 23:14:29 +00:00
|
|
|
|
|
|
|
Returns a 3-tuple: kind, keyid, pubkey_data
|
|
|
|
"""
|
|
|
|
|
2016-08-15 01:57:00 +00:00
|
|
|
if hint is None:
|
|
|
|
hint = expanduser('~/.ssh/')
|
|
|
|
else:
|
|
|
|
if not exists(hint):
|
2016-08-16 00:01:39 +00:00
|
|
|
raise PubkeyError("Can't find '{}'".format(hint))
|
2016-08-15 01:57:00 +00:00
|
|
|
|
|
|
|
pubkeys = [f for f in os.listdir(hint) if f.endswith('.pub')]
|
|
|
|
if len(pubkeys) == 0:
|
2016-08-16 00:01:39 +00:00
|
|
|
raise PubkeyError("No public keys in '{}'".format(hint))
|
2016-08-15 01:57:00 +00:00
|
|
|
elif len(pubkeys) > 1:
|
|
|
|
got_key = False
|
|
|
|
while not got_key:
|
|
|
|
ans = click.prompt(
|
|
|
|
"Multiple public-keys found:\n" + \
|
|
|
|
"\n".join([" {}: {}".format(a, b) for a, b in enumerate(pubkeys)]) + \
|
|
|
|
"\nSend which one?"
|
|
|
|
)
|
|
|
|
try:
|
|
|
|
ans = int(ans)
|
|
|
|
if ans < 0 or ans >= len(pubkeys):
|
|
|
|
ans = None
|
|
|
|
else:
|
|
|
|
got_key = True
|
|
|
|
with open(join(hint, pubkeys[ans]), 'r') as f:
|
|
|
|
pubkey = f.read()
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
got_key = False
|
|
|
|
else:
|
|
|
|
with open(join(hint, pubkeys[0]), 'r') as f:
|
|
|
|
pubkey = f.read()
|
2016-08-14 23:14:29 +00:00
|
|
|
parts = pubkey.strip().split()
|
|
|
|
kind = parts[0]
|
|
|
|
keyid = 'unknown' if len(parts) <= 2 else parts[2]
|
|
|
|
|
|
|
|
return kind, keyid, pubkey
|
|
|
|
|
|
|
|
|
|
|
|
@inlineCallbacks
|
2016-08-15 01:57:00 +00:00
|
|
|
def accept(cfg, reactor=reactor):
|
2016-08-14 23:14:29 +00:00
|
|
|
yield xfer_util.send(
|
|
|
|
reactor,
|
2016-12-22 20:44:13 +00:00
|
|
|
cfg.appid or u"lothar.com/wormhole/ssh-add",
|
2016-08-14 23:14:29 +00:00
|
|
|
cfg.relay_url,
|
|
|
|
data=cfg.public_key[2],
|
|
|
|
code=cfg.code,
|
|
|
|
use_tor=cfg.tor,
|
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-16 03:24:23 +00:00
|
|
|
launch_tor=cfg.launch_tor,
|
|
|
|
tor_control_port=cfg.tor_control_port,
|
2016-08-14 23:14:29 +00:00
|
|
|
)
|
|
|
|
print("Key sent.")
|
|
|
|
|
|
|
|
|
|
|
|
@inlineCallbacks
|
2016-08-15 01:57:00 +00:00
|
|
|
def invite(cfg, reactor=reactor):
|
2016-08-14 23:14:29 +00:00
|
|
|
|
|
|
|
def on_code_created(code):
|
|
|
|
print("Now tell the other user to run:")
|
|
|
|
print()
|
2016-08-15 01:57:00 +00:00
|
|
|
print("wormhole ssh accept {}".format(code))
|
2016-08-14 23:14:29 +00:00
|
|
|
print()
|
|
|
|
|
2016-08-15 01:57:00 +00:00
|
|
|
if cfg.ssh_user is None:
|
|
|
|
ssh_path = expanduser('~/.ssh/'.format(cfg.ssh_user))
|
|
|
|
else:
|
2016-08-16 00:24:05 +00:00
|
|
|
ssh_path = expanduser('~{}/.ssh/'.format(cfg.ssh_user))
|
2016-08-15 01:57:00 +00:00
|
|
|
auth_key_path = join(ssh_path, 'authorized_keys')
|
|
|
|
if not exists(auth_key_path):
|
|
|
|
print("Note: '{}' not found; will be created".format(auth_key_path))
|
|
|
|
if not exists(ssh_path):
|
|
|
|
print(" '{}' doesn't exist either".format(ssh_path))
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
open(auth_key_path, 'a').close()
|
|
|
|
except OSError:
|
|
|
|
print("No write permission on '{}'".format(auth_key_path))
|
|
|
|
return
|
|
|
|
try:
|
|
|
|
os.listdir(ssh_path)
|
|
|
|
except OSError:
|
|
|
|
print("Can't read '{}'".format(ssh_path))
|
|
|
|
return
|
|
|
|
|
2016-08-14 23:14:29 +00:00
|
|
|
pubkey = yield xfer_util.receive(
|
|
|
|
reactor,
|
2016-12-22 20:44:13 +00:00
|
|
|
cfg.appid or u"lothar.com/wormhole/ssh-add",
|
2016-08-14 23:14:29 +00:00
|
|
|
cfg.relay_url,
|
|
|
|
None, # allocate a code for us
|
|
|
|
use_tor=cfg.tor,
|
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-16 03:24:23 +00:00
|
|
|
launch_tor=cfg.launch_tor,
|
|
|
|
tor_control_port=cfg.tor_control_port,
|
2016-08-14 23:14:29 +00:00
|
|
|
on_code=on_code_created,
|
|
|
|
)
|
|
|
|
|
|
|
|
parts = pubkey.split()
|
|
|
|
kind = parts[0]
|
|
|
|
keyid = 'unknown' if len(parts) <= 2 else parts[2]
|
|
|
|
|
2016-08-15 01:57:00 +00:00
|
|
|
if not exists(auth_key_path):
|
|
|
|
if not exists(ssh_path):
|
|
|
|
os.mkdir(ssh_path, mode=0o700)
|
|
|
|
with open(auth_key_path, 'a', 0o600) as f:
|
|
|
|
f.write('{}\n'.format(pubkey.strip()))
|
|
|
|
print("Appended key type='{kind}' id='{key_id}' to '{auth_file}'".format(
|
|
|
|
kind=kind, key_id=keyid, auth_file=auth_key_path))
|