add "wormhole" entrypoint script. requires twisted.
I'm using Twisted for the subcommand argument parsing. It might be nice to use something smaller.
This commit is contained in:
parent
5682ddff8e
commit
7a99c04d64
|
@ -1,73 +0,0 @@
|
|||
from __future__ import print_function
|
||||
import sys, os, json
|
||||
from nacl.secret import SecretBox
|
||||
from wormhole.blocking.transcribe import Receiver, WrongPasswordError
|
||||
from wormhole.blocking.transit import TransitReceiver
|
||||
|
||||
APPID = "lothar.com/wormhole/file-xfer"
|
||||
|
||||
# we're receiving
|
||||
transit_receiver = TransitReceiver()
|
||||
|
||||
mydata = json.dumps({
|
||||
"transit": {
|
||||
"direct_connection_hints": transit_receiver.get_direct_hints(),
|
||||
"relay_connection_hints": transit_receiver.get_relay_hints(),
|
||||
},
|
||||
}).encode("utf-8")
|
||||
r = Receiver(APPID, mydata)
|
||||
r.set_code(r.input_code("Enter receive-file wormhole code: "))
|
||||
|
||||
try:
|
||||
data = json.loads(r.get_data().decode("utf-8"))
|
||||
except WrongPasswordError as e:
|
||||
print("ERROR: " + e.explain(), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
#print("their data: %r" % (data,))
|
||||
|
||||
file_data = data["file"]
|
||||
xfer_key = r.derive_key(APPID+"/xfer-key", SecretBox.KEY_SIZE)
|
||||
filename = os.path.basename(file_data["filename"]) # unicode
|
||||
filesize = file_data["filesize"]
|
||||
encrypted_filesize = filesize + SecretBox.NONCE_SIZE+16
|
||||
|
||||
# now receive the rest of the owl
|
||||
tdata = data["transit"]
|
||||
transit_key = r.derive_key(APPID+"/transit-key")
|
||||
transit_receiver.set_transit_key(transit_key)
|
||||
transit_receiver.add_their_direct_hints(tdata["direct_connection_hints"])
|
||||
transit_receiver.add_their_relay_hints(tdata["relay_connection_hints"])
|
||||
skt = transit_receiver.establish_connection()
|
||||
print("Receiving %d bytes.." % filesize)
|
||||
encrypted = b""
|
||||
while len(encrypted) < encrypted_filesize:
|
||||
more = skt.recv(encrypted_filesize - len(encrypted))
|
||||
if not more:
|
||||
print("Connection dropped before full file received")
|
||||
print("got %d bytes, wanted %d" % (len(encrypted), encrypted_filesize))
|
||||
sys.exit(1)
|
||||
encrypted += more
|
||||
assert len(encrypted) == encrypted_filesize
|
||||
|
||||
decrypted = SecretBox(xfer_key).decrypt(encrypted)
|
||||
|
||||
# only write to the current directory, and never overwrite anything
|
||||
here = os.path.abspath(os.getcwd())
|
||||
target = os.path.abspath(os.path.join(here, filename))
|
||||
if os.path.dirname(target) != here:
|
||||
print("Error: suggested filename (%s) would be outside current directory"
|
||||
% (filename,))
|
||||
skt.send("bad filename\n")
|
||||
skt.close()
|
||||
sys.exit(1)
|
||||
if os.path.exists(target):
|
||||
print("Error: refusing to overwrite existing file %s" % (filename,))
|
||||
skt.send("file already exists\n")
|
||||
skt.close()
|
||||
sys.exit(1)
|
||||
with open(target, "wb") as f:
|
||||
f.write(decrypted)
|
||||
print("Received file written to %s" % filename)
|
||||
skt.send("ok\n")
|
||||
skt.close()
|
||||
sys.exit(0)
|
|
@ -1,19 +0,0 @@
|
|||
from __future__ import print_function
|
||||
import sys, time, json
|
||||
from wormhole.blocking.transcribe import Receiver, WrongPasswordError
|
||||
|
||||
APPID = "lothar.com/wormhole/text-xfer"
|
||||
|
||||
# we're receiving
|
||||
data = json.dumps({"message": "ok"}).encode("utf-8")
|
||||
r = Receiver(APPID, data)
|
||||
r.set_code(r.input_code("Enter receive-text wormhole code: "))
|
||||
start = time.time()
|
||||
try:
|
||||
them_bytes = r.get_data()
|
||||
except WrongPasswordError as e:
|
||||
print("ERROR: " + e.explain(), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
them_d = json.loads(them_bytes.decode("utf-8"))
|
||||
print(them_d["message"])
|
||||
print("elapsed time: %.2f" % (time.time() - start))
|
|
@ -1,68 +0,0 @@
|
|||
from __future__ import print_function
|
||||
import os, sys, json
|
||||
from nacl.secret import SecretBox
|
||||
from wormhole.blocking.transcribe import Initiator, WrongPasswordError
|
||||
from wormhole.blocking.transit import TransitSender
|
||||
|
||||
APPID = "lothar.com/wormhole/file-xfer"
|
||||
|
||||
# we're sending
|
||||
filename = sys.argv[1]
|
||||
assert os.path.isfile(filename)
|
||||
transit_sender = TransitSender()
|
||||
|
||||
filesize = os.stat(filename).st_size
|
||||
data = json.dumps({
|
||||
"file": {
|
||||
"filename": os.path.basename(filename),
|
||||
"filesize": filesize,
|
||||
},
|
||||
"transit": {
|
||||
"direct_connection_hints": transit_sender.get_direct_hints(),
|
||||
"relay_connection_hints": transit_sender.get_relay_hints(),
|
||||
},
|
||||
}).encode("utf-8")
|
||||
|
||||
i = Initiator(APPID, data)
|
||||
code = i.get_code()
|
||||
print("On the other computer, please run: receive_file")
|
||||
print("Wormhole code is '%s'" % code)
|
||||
print("")
|
||||
try:
|
||||
them_bytes = i.get_data()
|
||||
except WrongPasswordError as e:
|
||||
print("ERROR: " + e.explain(), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
them_d = json.loads(them_bytes.decode("utf-8"))
|
||||
#print("them: %r" % (them_d,))
|
||||
xfer_key = i.derive_key(APPID+"/xfer-key", SecretBox.KEY_SIZE)
|
||||
|
||||
box = SecretBox(xfer_key)
|
||||
with open(filename, "rb") as f:
|
||||
plaintext = f.read()
|
||||
nonce = os.urandom(SecretBox.NONCE_SIZE)
|
||||
encrypted = box.encrypt(plaintext, nonce)
|
||||
|
||||
tdata = them_d["transit"]
|
||||
transit_key = i.derive_key(APPID+"/transit-key")
|
||||
transit_sender.set_transit_key(transit_key)
|
||||
transit_sender.add_their_direct_hints(tdata["direct_connection_hints"])
|
||||
transit_sender.add_their_relay_hints(tdata["relay_connection_hints"])
|
||||
skt = transit_sender.establish_connection()
|
||||
|
||||
print("Sending %d bytes.." % filesize)
|
||||
sent = 0
|
||||
while sent < len(encrypted):
|
||||
more = skt.send(encrypted[sent:])
|
||||
sent += more
|
||||
|
||||
print("File sent.. waiting for confirmation")
|
||||
# ack is a short newline-terminated string, followed by socket close. A long
|
||||
# read is probably good enough.
|
||||
ack = skt.recv(300)
|
||||
if ack == "ok\n":
|
||||
print("Confirmation received. Transfer complete.")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("Transfer failed (remote says: %r)" % ack)
|
||||
sys.exit(1)
|
|
@ -1,22 +0,0 @@
|
|||
from __future__ import print_function
|
||||
import sys, json
|
||||
from wormhole.blocking.transcribe import Initiator, WrongPasswordError
|
||||
|
||||
APPID = "lothar.com/wormhole/text-xfer"
|
||||
|
||||
# we're sending
|
||||
message = sys.argv[1]
|
||||
data = json.dumps({"message": message,
|
||||
}).encode("utf-8")
|
||||
i = Initiator(APPID, data)
|
||||
code = i.get_code()
|
||||
print("On the other computer, please run: receive_text")
|
||||
print("Wormhole code is: %s" % code)
|
||||
print("")
|
||||
try:
|
||||
them_bytes = i.get_data()
|
||||
except WrongPasswordError as e:
|
||||
print("ERROR: " + e.explain(), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
them_d = json.loads(them_bytes.decode("utf-8"))
|
||||
print("them: %r" % (them_d,))
|
4
setup.py
4
setup.py
|
@ -19,7 +19,9 @@ setup(name="wormhole-sync",
|
|||
url="https://github.com/warner/wormhole-sync",
|
||||
package_dir={"": "src"},
|
||||
packages=["wormhole"],
|
||||
install_requires=["spake2", "pynacl", "requests"],
|
||||
entry_points={"console_scripts":
|
||||
["wormhole = wormhole.scripts.runner:entry"]},
|
||||
install_requires=["spake2", "pynacl", "requests", "twisted"],
|
||||
test_suite="wormhole.test",
|
||||
cmdclass=commands,
|
||||
)
|
||||
|
|
0
src/wormhole/scripts/__init__.py
Normal file
0
src/wormhole/scripts/__init__.py
Normal file
74
src/wormhole/scripts/cmd_receive_file.py
Normal file
74
src/wormhole/scripts/cmd_receive_file.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
from __future__ import print_function
|
||||
import sys, os, json
|
||||
from nacl.secret import SecretBox
|
||||
from wormhole.blocking.transcribe import Receiver, WrongPasswordError
|
||||
from wormhole.blocking.transit import TransitReceiver
|
||||
|
||||
APPID = "lothar.com/wormhole/file-xfer"
|
||||
|
||||
def receive_file(so):
|
||||
# we're receiving
|
||||
transit_receiver = TransitReceiver()
|
||||
|
||||
mydata = json.dumps({
|
||||
"transit": {
|
||||
"direct_connection_hints": transit_receiver.get_direct_hints(),
|
||||
"relay_connection_hints": transit_receiver.get_relay_hints(),
|
||||
},
|
||||
}).encode("utf-8")
|
||||
r = Receiver(APPID, mydata)
|
||||
r.set_code(r.input_code("Enter receive-file wormhole code: "))
|
||||
|
||||
try:
|
||||
data = json.loads(r.get_data().decode("utf-8"))
|
||||
except WrongPasswordError as e:
|
||||
print("ERROR: " + e.explain(), file=sys.stderr)
|
||||
return 1
|
||||
#print("their data: %r" % (data,))
|
||||
|
||||
file_data = data["file"]
|
||||
xfer_key = r.derive_key(APPID+"/xfer-key", SecretBox.KEY_SIZE)
|
||||
filename = os.path.basename(file_data["filename"]) # unicode
|
||||
filesize = file_data["filesize"]
|
||||
encrypted_filesize = filesize + SecretBox.NONCE_SIZE+16
|
||||
|
||||
# now receive the rest of the owl
|
||||
tdata = data["transit"]
|
||||
transit_key = r.derive_key(APPID+"/transit-key")
|
||||
transit_receiver.set_transit_key(transit_key)
|
||||
transit_receiver.add_their_direct_hints(tdata["direct_connection_hints"])
|
||||
transit_receiver.add_their_relay_hints(tdata["relay_connection_hints"])
|
||||
skt = transit_receiver.establish_connection()
|
||||
print("Receiving %d bytes.." % filesize)
|
||||
encrypted = b""
|
||||
while len(encrypted) < encrypted_filesize:
|
||||
more = skt.recv(encrypted_filesize - len(encrypted))
|
||||
if not more:
|
||||
print("Connection dropped before full file received")
|
||||
print("got %d bytes, wanted %d" % (len(encrypted), encrypted_filesize))
|
||||
return 1
|
||||
encrypted += more
|
||||
assert len(encrypted) == encrypted_filesize
|
||||
|
||||
decrypted = SecretBox(xfer_key).decrypt(encrypted)
|
||||
|
||||
# only write to the current directory, and never overwrite anything
|
||||
here = os.path.abspath(os.getcwd())
|
||||
target = os.path.abspath(os.path.join(here, filename))
|
||||
if os.path.dirname(target) != here:
|
||||
print("Error: suggested filename (%s) would be outside current directory"
|
||||
% (filename,))
|
||||
skt.send("bad filename\n")
|
||||
skt.close()
|
||||
return 1
|
||||
if os.path.exists(target):
|
||||
print("Error: refusing to overwrite existing file %s" % (filename,))
|
||||
skt.send("file already exists\n")
|
||||
skt.close()
|
||||
return 1
|
||||
with open(target, "wb") as f:
|
||||
f.write(decrypted)
|
||||
print("Received file written to %s" % filename)
|
||||
skt.send("ok\n")
|
||||
skt.close()
|
||||
return 0
|
20
src/wormhole/scripts/cmd_receive_text.py
Normal file
20
src/wormhole/scripts/cmd_receive_text.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from __future__ import print_function
|
||||
import sys, time, json
|
||||
from wormhole.blocking.transcribe import Receiver, WrongPasswordError
|
||||
|
||||
APPID = "lothar.com/wormhole/text-xfer"
|
||||
|
||||
def receive_text(so):
|
||||
# we're receiving
|
||||
data = json.dumps({"message": "ok"}).encode("utf-8")
|
||||
r = Receiver(APPID, data)
|
||||
r.set_code(r.input_code("Enter receive-text wormhole code: "))
|
||||
start = time.time()
|
||||
try:
|
||||
them_bytes = r.get_data()
|
||||
except WrongPasswordError as e:
|
||||
print("ERROR: " + e.explain(), file=sys.stderr)
|
||||
return 1
|
||||
them_d = json.loads(them_bytes.decode("utf-8"))
|
||||
print(them_d["message"])
|
||||
print("elapsed time: %.2f" % (time.time() - start))
|
69
src/wormhole/scripts/cmd_send_file.py
Normal file
69
src/wormhole/scripts/cmd_send_file.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
from __future__ import print_function
|
||||
import os, sys, json
|
||||
from nacl.secret import SecretBox
|
||||
from wormhole.blocking.transcribe import Initiator, WrongPasswordError
|
||||
from wormhole.blocking.transit import TransitSender
|
||||
|
||||
APPID = "lothar.com/wormhole/file-xfer"
|
||||
|
||||
def send_file(so):
|
||||
# we're sending
|
||||
filename = so["filename"]
|
||||
assert os.path.isfile(filename)
|
||||
transit_sender = TransitSender()
|
||||
|
||||
filesize = os.stat(filename).st_size
|
||||
data = json.dumps({
|
||||
"file": {
|
||||
"filename": os.path.basename(filename),
|
||||
"filesize": filesize,
|
||||
},
|
||||
"transit": {
|
||||
"direct_connection_hints": transit_sender.get_direct_hints(),
|
||||
"relay_connection_hints": transit_sender.get_relay_hints(),
|
||||
},
|
||||
}).encode("utf-8")
|
||||
|
||||
i = Initiator(APPID, data)
|
||||
code = i.get_code()
|
||||
print("On the other computer, please run: receive_file")
|
||||
print("Wormhole code is '%s'" % code)
|
||||
print("")
|
||||
try:
|
||||
them_bytes = i.get_data()
|
||||
except WrongPasswordError as e:
|
||||
print("ERROR: " + e.explain(), file=sys.stderr)
|
||||
return 1
|
||||
them_d = json.loads(them_bytes.decode("utf-8"))
|
||||
#print("them: %r" % (them_d,))
|
||||
xfer_key = i.derive_key(APPID+"/xfer-key", SecretBox.KEY_SIZE)
|
||||
|
||||
box = SecretBox(xfer_key)
|
||||
with open(filename, "rb") as f:
|
||||
plaintext = f.read()
|
||||
nonce = os.urandom(SecretBox.NONCE_SIZE)
|
||||
encrypted = box.encrypt(plaintext, nonce)
|
||||
|
||||
tdata = them_d["transit"]
|
||||
transit_key = i.derive_key(APPID+"/transit-key")
|
||||
transit_sender.set_transit_key(transit_key)
|
||||
transit_sender.add_their_direct_hints(tdata["direct_connection_hints"])
|
||||
transit_sender.add_their_relay_hints(tdata["relay_connection_hints"])
|
||||
skt = transit_sender.establish_connection()
|
||||
|
||||
print("Sending %d bytes.." % filesize)
|
||||
sent = 0
|
||||
while sent < len(encrypted):
|
||||
more = skt.send(encrypted[sent:])
|
||||
sent += more
|
||||
|
||||
print("File sent.. waiting for confirmation")
|
||||
# ack is a short newline-terminated string, followed by socket close. A long
|
||||
# read is probably good enough.
|
||||
ack = skt.recv(300)
|
||||
if ack == "ok\n":
|
||||
print("Confirmation received. Transfer complete.")
|
||||
return 0
|
||||
else:
|
||||
print("Transfer failed (remote says: %r)" % ack)
|
||||
return 1
|
23
src/wormhole/scripts/cmd_send_text.py
Normal file
23
src/wormhole/scripts/cmd_send_text.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from __future__ import print_function
|
||||
import sys, json
|
||||
from wormhole.blocking.transcribe import Initiator, WrongPasswordError
|
||||
|
||||
APPID = "lothar.com/wormhole/text-xfer"
|
||||
|
||||
def send_text(so):
|
||||
# we're sending
|
||||
message = so["text"]
|
||||
data = json.dumps({"message": message,
|
||||
}).encode("utf-8")
|
||||
i = Initiator(APPID, data)
|
||||
code = i.get_code()
|
||||
print("On the other computer, please run: receive_text")
|
||||
print("Wormhole code is: %s" % code)
|
||||
print("")
|
||||
try:
|
||||
them_bytes = i.get_data()
|
||||
except WrongPasswordError as e:
|
||||
print("ERROR: " + e.explain(), file=sys.stderr)
|
||||
return 1
|
||||
them_d = json.loads(them_bytes.decode("utf-8"))
|
||||
print("them: %r" % (them_d,))
|
88
src/wormhole/scripts/runner.py
Normal file
88
src/wormhole/scripts/runner.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
import sys
|
||||
from twisted.python import usage
|
||||
|
||||
class SendTextOptions(usage.Options):
|
||||
def parseArgs(self, text):
|
||||
self["text"] = text
|
||||
synopsis = "TEXT"
|
||||
|
||||
class ReceiveTextOptions(usage.Options):
|
||||
synopsis = ""
|
||||
|
||||
class SendFileOptions(usage.Options):
|
||||
def parseArgs(self, filename):
|
||||
self["filename"] = filename
|
||||
synopsis = "FILENAME"
|
||||
|
||||
class ReceiveFileOptions(usage.Options):
|
||||
synopsis = ""
|
||||
|
||||
class Options(usage.Options):
|
||||
synopsis = "\nUsage: wormhole <command>"
|
||||
subCommands = [("send-text", None, SendTextOptions, "Send a text message"),
|
||||
("send-file", None, SendFileOptions, "Send a file"),
|
||||
("receive-text", None, ReceiveTextOptions, "Receive a text message"),
|
||||
("receive-file", None, ReceiveFileOptions, "Receive a file"),
|
||||
]
|
||||
|
||||
def getUsage(self, **kwargs):
|
||||
t = usage.Options.getUsage(self, **kwargs)
|
||||
return t + "\nPlease run 'wormhole <command> --help' for more details on each command.\n"
|
||||
|
||||
def postOptions(self):
|
||||
if not hasattr(self, 'subOptions'):
|
||||
raise usage.UsageError("must specify a command")
|
||||
|
||||
def send_text(*args):
|
||||
from . import cmd_send_text
|
||||
return cmd_send_text.send_text(*args)
|
||||
|
||||
def receive_text(*args):
|
||||
from . import cmd_receive_text
|
||||
return cmd_receive_text.receive_text(*args)
|
||||
|
||||
def send_file(*args):
|
||||
from . import cmd_send_file
|
||||
return cmd_send_file.send_file(*args)
|
||||
|
||||
def receive_file(*args):
|
||||
from . import cmd_receive_file
|
||||
return cmd_receive_file.receive_file(*args)
|
||||
|
||||
DISPATCH = {"send-text": send_text,
|
||||
"receive-text": receive_text,
|
||||
"send-file": send_file,
|
||||
"receive-file": receive_file,
|
||||
}
|
||||
|
||||
def run(args, stdout, stderr, executable=None):
|
||||
"""This is invoked directly by the 'wormhole' entry-point script. It can
|
||||
also invoked by entry() below."""
|
||||
config = Options()
|
||||
try:
|
||||
config.parseOptions(args)
|
||||
except usage.error, e:
|
||||
c = config
|
||||
while hasattr(c, 'subOptions'):
|
||||
c = c.subOptions
|
||||
print >>stderr, str(c)
|
||||
print >>stderr, e.args[0]
|
||||
return 1
|
||||
command = config.subCommand
|
||||
so = config.subOptions
|
||||
so["executable"] = executable
|
||||
try:
|
||||
#rc = DISPATCH[command](so, stdout, stderr)
|
||||
rc = DISPATCH[command](so)
|
||||
return rc
|
||||
except ImportError, e:
|
||||
print >>stderr, "--- ImportError ---"
|
||||
print >>stderr, e
|
||||
print >>stderr, "Please run 'python setup.py build'"
|
||||
raise
|
||||
return 1
|
||||
|
||||
def entry():
|
||||
"""This is used by a setuptools entry_point. When invoked this way,
|
||||
setuptools has already put the installed package on sys.path ."""
|
||||
return run(sys.argv[1:], sys.stdout, sys.stderr, executable=sys.argv[0])
|
Loading…
Reference in New Issue
Block a user