Reticulum/RNS/Utilities/rncp.py

789 lines
30 KiB
Python
Raw Normal View History

2022-05-22 21:44:32 +00:00
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import RNS
import argparse
2023-09-18 14:36:58 +00:00
import threading
2022-05-22 21:44:32 +00:00
import time
import sys
import os
from RNS._version import __version__
APP_NAME = "rncp"
allow_all = False
allow_fetch = False
fetch_jail = None
2022-05-22 21:44:32 +00:00
allowed_identity_hashes = []
REQ_FETCH_NOT_ALLOWED = 0xF0
def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identity = False,
limit = None, disable_auth = None, fetch_allowed = False, jail = None, announce = False):
global allow_all, allow_fetch, allowed_identity_hashes, fetch_jail
2023-09-18 20:22:44 +00:00
from tempfile import TemporaryFile
allow_fetch = fetch_allowed
2022-05-22 21:44:32 +00:00
identity = None
2023-09-18 14:36:58 +00:00
if announce < 0:
announce = False
2022-05-22 21:44:32 +00:00
targetloglevel = 3+verbosity-quietness
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
if jail != None:
fetch_jail = os.path.abspath(os.path.expanduser(jail))
2024-10-07 08:44:18 +00:00
RNS.log(f"Restricting fetch requests to paths under \"{fetch_jail}\"", RNS.LOG_VERBOSE)
2024-10-07 08:44:18 +00:00
identity_path = f"{RNS.Reticulum.identitypath}/{APP_NAME}"
2022-05-22 21:44:32 +00:00
if os.path.isfile(identity_path):
2024-10-07 08:52:43 +00:00
identity = RNS.Identity.from_file(identity_path)
2022-05-22 21:44:32 +00:00
if identity == None:
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
identity = RNS.Identity()
identity.to_file(identity_path)
destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "receive")
if display_identity:
2024-10-07 08:44:18 +00:00
print(f"Identity : {identity}")
print(f"Listening on : {RNS.prettyhexrep(destination.hash)}")
2022-05-22 21:44:32 +00:00
exit(0)
if disable_auth:
allow_all = True
else:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
try:
allowed_file_name = "allowed_identities"
allowed_file = None
2024-10-07 08:44:18 +00:00
if os.path.isfile(os.path.expanduser(f"/etc/rncp/{allowed_file_name}")):
allowed_file = os.path.expanduser(f"/etc/rncp/{allowed_file_name}")
elif os.path.isfile(os.path.expanduser(f"~/.config/rncp/{allowed_file_name}")):
allowed_file = os.path.expanduser(f"~/.config/rncp/{allowed_file_name}")
elif os.path.isfile(os.path.expanduser(f"~/.rncp/{allowed_file_name}")):
allowed_file = os.path.expanduser(f"~/.rncp/{allowed_file_name}")
if allowed_file != None:
2024-10-07 08:44:18 +00:00
af = open(allowed_file)
al = af.read().replace("\r", "").split("\n")
ali = []
for a in al:
if len(a) == dest_len:
ali.append(a)
if len(ali) > 0:
if not allowed:
allowed = ali
else:
allowed.extend(ali)
2023-09-18 20:22:44 +00:00
if len(ali) == 1:
ms = "y"
else:
ms = "ies"
2024-10-07 08:52:43 +00:00
2024-10-07 08:44:18 +00:00
RNS.log(f"Loaded {len(ali)} allowed identit{ms} from {allowed_file}", RNS.LOG_VERBOSE)
except Exception as e:
2024-10-07 08:44:18 +00:00
RNS.log(f"Error while parsing allowed_identities file. The contained exception was: {e}", RNS.LOG_ERROR)
2022-05-22 21:44:32 +00:00
if allowed != None:
for a in allowed:
try:
if len(a) != dest_len:
2024-10-07 08:44:18 +00:00
raise ValueError(f"Allowed destination length is invalid, must be {dest_len} hexadecimal characters ({dest_len // 2} bytes).")
2022-05-22 21:44:32 +00:00
try:
destination_hash = bytes.fromhex(a)
allowed_identity_hashes.append(destination_hash)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
exit(1)
if len(allowed_identity_hashes) < 1 and not disable_auth:
print("Warning: No allowed identities configured, rncp will not accept any files!")
2023-09-18 20:22:44 +00:00
def fetch_request(path, data, request_id, link_id, remote_identity, requested_at):
global allow_fetch, fetch_jail
if not allow_fetch:
return REQ_FETCH_NOT_ALLOWED
file_path = os.path.abspath(os.path.expanduser(data))
if fetch_jail:
if not file_path.startswith(jail):
return REQ_FETCH_NOT_ALLOWED
2023-09-18 20:22:44 +00:00
target_link = None
for link in RNS.Transport.active_links:
if link.link_id == link_id:
target_link = link
if not os.path.isfile(file_path):
2024-10-07 08:44:18 +00:00
RNS.log(f"Client-requested file not found: {file_path}", RNS.LOG_VERBOSE)
2023-09-18 20:22:44 +00:00
return False
else:
if target_link != None:
2024-10-07 08:44:18 +00:00
RNS.log(f"Sending file {file_path} to client", RNS.LOG_VERBOSE)
2023-09-18 20:22:44 +00:00
temp_file = TemporaryFile()
real_file = open(file_path, "rb")
filename_bytes = os.path.basename(file_path).encode("utf-8")
filename_len = len(filename_bytes)
if filename_len > 0xFFFF:
print("Filename exceeds max size, cannot send")
exit(1)
temp_file.write(filename_len.to_bytes(2, "big"))
temp_file.write(filename_bytes)
temp_file.write(real_file.read())
temp_file.seek(0)
fetch_resource = RNS.Resource(temp_file, target_link)
return True
else:
return None
destination.set_link_established_callback(client_link_established)
destination.register_request_handler("fetch_file", response_generator=fetch_request, allow=RNS.Destination.ALLOW_LIST, allowed_list=allowed_identity_hashes)
2024-10-07 08:44:18 +00:00
print(f"rncp listening on {RNS.prettyhexrep(destination.hash)}")
2022-05-22 21:44:32 +00:00
2023-09-18 14:36:58 +00:00
if announce >= 0:
def job():
destination.announce()
if announce > 0:
while True:
time.sleep(announce)
destination.announce()
threading.Thread(target=job, daemon=True).start()
2024-10-07 08:52:43 +00:00
2022-05-22 21:44:32 +00:00
while True:
time.sleep(1)
2023-09-18 20:22:44 +00:00
def client_link_established(link):
2022-05-22 21:44:32 +00:00
RNS.log("Incoming link established", RNS.LOG_VERBOSE)
link.set_remote_identified_callback(receive_sender_identified)
link.set_resource_strategy(RNS.Link.ACCEPT_APP)
link.set_resource_callback(receive_resource_callback)
link.set_resource_started_callback(receive_resource_started)
link.set_resource_concluded_callback(receive_resource_concluded)
def receive_sender_identified(link, identity):
2022-07-02 07:48:15 +00:00
global allow_all
2022-05-22 21:44:32 +00:00
if identity.hash in allowed_identity_hashes:
RNS.log("Authenticated sender", RNS.LOG_VERBOSE)
else:
2022-07-02 07:33:05 +00:00
if not allow_all:
RNS.log("Sender not allowed, tearing down link", RNS.LOG_VERBOSE)
link.teardown()
else:
pass
2022-05-22 21:44:32 +00:00
def receive_resource_callback(resource):
2022-07-02 07:48:15 +00:00
global allow_all
2024-10-07 08:52:43 +00:00
2022-05-22 21:44:32 +00:00
sender_identity = resource.link.get_remote_identity()
if sender_identity != None:
if sender_identity.hash in allowed_identity_hashes:
return True
2022-07-02 07:48:15 +00:00
if allow_all:
return True
2022-05-22 21:44:32 +00:00
return False
def receive_resource_started(resource):
if resource.link.get_remote_identity():
2024-10-07 08:44:18 +00:00
id_str = f" from {RNS.prettyhexrep(resource.link.get_remote_identity().hash)}"
2022-05-22 21:44:32 +00:00
else:
id_str = ""
2024-10-07 08:44:18 +00:00
print(f"Starting resource transfer {RNS.prettyhexrep(resource.hash)}{id_str}")
2022-05-22 21:44:32 +00:00
def receive_resource_concluded(resource):
if resource.status == RNS.Resource.COMPLETE:
2024-10-07 08:44:18 +00:00
print(f"{resource} completed")
2022-05-22 21:44:32 +00:00
if resource.total_size > 4:
filename_len = int.from_bytes(resource.data.read(2), "big")
filename = resource.data.read(filename_len).decode("utf-8")
counter = 0
saved_filename = filename
while os.path.isfile(saved_filename):
counter += 1
2024-10-07 08:44:18 +00:00
saved_filename = f"{filename}.{counter}"
2024-10-07 08:52:43 +00:00
2022-05-22 21:44:32 +00:00
file = open(saved_filename, "wb")
file.write(resource.data.read())
file.close()
else:
print("Invalid data received, ignoring resource")
else:
print("Resource failed")
resource_done = False
current_resource = None
stats = []
speed = 0.0
def sender_progress(resource):
stats_max = 32
global current_resource, stats, speed, resource_done
current_resource = resource
now = time.time()
got = current_resource.get_progress()*current_resource.total_size
entry = [now, got]
stats.append(entry)
while len(stats) > stats_max:
stats.pop(0)
span = now - stats[0][0]
if span == 0:
speed = 0
else:
diff = got - stats[0][1]
speed = diff/span
if resource.status < RNS.Resource.COMPLETE:
resource_done = False
else:
resource_done = True
link = None
2023-09-18 20:22:44 +00:00
def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False):
global current_resource, resource_done, link, speed
targetloglevel = 3+verbosity-quietness
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination) != dest_len:
2024-10-07 08:44:18 +00:00
raise ValueError(f"Allowed destination length is invalid, must be {dest_len} hexadecimal characters ({dest_len // 2} bytes).")
2023-09-18 20:22:44 +00:00
try:
destination_hash = bytes.fromhex(destination)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
exit(1)
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
2024-10-07 08:44:18 +00:00
identity_path = f"{RNS.Reticulum.identitypath}/{APP_NAME}"
2023-09-18 20:22:44 +00:00
if os.path.isfile(identity_path):
identity = RNS.Identity.from_file(identity_path)
if identity == None:
2024-10-07 08:44:18 +00:00
RNS.log(f"Could not load identity for rncp. The identity file at \"{identity_path}\" may be corrupt or unreadable.", RNS.LOG_ERROR)
2023-09-18 20:22:44 +00:00
exit(2)
else:
identity = None
if identity == None:
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
identity = RNS.Identity()
identity.to_file(identity_path)
if not RNS.Transport.has_path(destination_hash):
RNS.Transport.request_path(destination_hash)
if silent:
2024-10-07 08:44:18 +00:00
print(f"Path to {RNS.prettyhexrep(destination_hash)} requested")
2023-09-18 20:22:44 +00:00
else:
2024-10-07 08:44:18 +00:00
print(f"Path to {RNS.prettyhexrep(destination_hash)} requested ", end=" ")
2023-09-18 20:22:44 +00:00
sys.stdout.flush()
i = 0
syms = "⢄⢂⢁⡁⡈⡐⡠"
estab_timeout = time.time()+timeout
while not RNS.Transport.has_path(destination_hash) and time.time() < estab_timeout:
if not silent:
time.sleep(0.1)
2024-10-07 08:44:18 +00:00
print(f"\b\b{syms[i]} ", end="")
2023-09-18 20:22:44 +00:00
sys.stdout.flush()
i = (i+1)%len(syms)
if not RNS.Transport.has_path(destination_hash):
if silent:
print("Path not found")
else:
print("\r \rPath not found")
exit(1)
else:
if silent:
2024-10-07 08:44:18 +00:00
print(f"Establishing link with {RNS.prettyhexrep(destination_hash)}")
2023-09-18 20:22:44 +00:00
else:
2024-10-07 08:44:18 +00:00
print(f'\r \rEstablishing link with {RNS.prettyhexrep(destination_hash)} ', end=" ")
2023-09-18 20:22:44 +00:00
listener_identity = RNS.Identity.recall(destination_hash)
listener_destination = RNS.Destination(
listener_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
APP_NAME,
"receive"
)
link = RNS.Link(listener_destination)
while link.status != RNS.Link.ACTIVE and time.time() < estab_timeout:
if not silent:
time.sleep(0.1)
2024-10-07 08:44:18 +00:00
print(f"\b\b{syms[i]} ", end="")
2023-09-18 20:22:44 +00:00
sys.stdout.flush()
i = (i+1)%len(syms)
if not RNS.Transport.has_path(destination_hash):
if silent:
2024-10-07 08:44:18 +00:00
print(f"Could not establish link with {RNS.prettyhexrep(destination_hash)}")
2023-09-18 20:22:44 +00:00
else:
2024-10-07 08:44:18 +00:00
print(f'\r \rCould not establish link with {RNS.prettyhexrep(destination_hash)}')
2023-09-18 20:22:44 +00:00
exit(1)
else:
if silent:
print("Requesting file from remote...")
else:
print("\r \rRequesting file from remote ", end=" ")
link.identify(identity)
request_resolved = False
request_status = "unknown"
resource_resolved = False
resource_status = "unrequested"
current_resource = None
def request_response(request_receipt):
nonlocal request_resolved, request_status
if request_receipt.response == False:
request_status = "not_found"
elif request_receipt.response == None:
request_status = "remote_error"
elif request_receipt.response == REQ_FETCH_NOT_ALLOWED:
request_status = "fetch_not_allowed"
2023-09-18 20:22:44 +00:00
else:
request_status = "found"
request_resolved = True
def request_failed(request_receipt):
nonlocal request_resolved, request_status
request_status = "unknown"
request_resolved = True
def fetch_resource_started(resource):
nonlocal resource_status
current_resource = resource
current_resource.progress_callback(sender_progress)
resource_status = "started"
def fetch_resource_concluded(resource):
nonlocal resource_resolved, resource_status
if resource.status == RNS.Resource.COMPLETE:
if resource.total_size > 4:
filename_len = int.from_bytes(resource.data.read(2), "big")
filename = resource.data.read(filename_len).decode("utf-8")
counter = 0
saved_filename = filename
while os.path.isfile(saved_filename):
counter += 1
2024-10-07 08:44:18 +00:00
saved_filename = f"{filename}.{counter}"
2024-10-07 08:52:43 +00:00
2023-09-18 20:22:44 +00:00
file = open(saved_filename, "wb")
file.write(resource.data.read())
file.close()
resource_status = "completed"
else:
print("Invalid data received, ignoring resource")
resource_status = "invalid_data"
else:
print("Resource failed")
resource_status = "failed"
resource_resolved = True
link.set_resource_strategy(RNS.Link.ACCEPT_ALL)
link.set_resource_started_callback(fetch_resource_started)
link.set_resource_concluded_callback(fetch_resource_concluded)
link.request("fetch_file", data=file, response_callback=request_response, failed_callback=request_failed)
syms = "⢄⢂⢁⡁⡈⡐⡠"
while not request_resolved:
if not silent:
time.sleep(0.1)
2024-10-07 08:44:18 +00:00
print(f"\b\b{syms[i]} ", end="")
2023-09-18 20:22:44 +00:00
sys.stdout.flush()
i = (i+1)%len(syms)
if request_status == "fetch_not_allowed":
if not silent: print("\r \r", end="")
2024-10-07 08:44:18 +00:00
print(f"Fetch request failed, fetching the file {file} was not allowed by the remote")
link.teardown()
time.sleep(0.15)
exit(0)
elif request_status == "not_found":
2023-09-18 20:22:44 +00:00
if not silent: print("\r \r", end="")
2024-10-07 08:44:18 +00:00
print(f"Fetch request failed, the file {file} was not found on the remote")
2023-09-18 20:22:44 +00:00
link.teardown()
time.sleep(0.15)
2023-09-18 20:22:44 +00:00
exit(0)
elif request_status == "remote_error":
if not silent: print("\r \r", end="")
print("Fetch request failed due to an error on the remote system")
link.teardown()
time.sleep(0.15)
2023-09-18 20:22:44 +00:00
exit(0)
elif request_status == "unknown":
if not silent: print("\r \r", end="")
2023-09-18 20:40:29 +00:00
print("Fetch request failed due to an unknown error (probably not authorised)")
2023-09-18 20:22:44 +00:00
link.teardown()
time.sleep(0.15)
2023-09-18 20:22:44 +00:00
exit(0)
elif request_status == "found":
if not silent: print("\r \r", end="")
while not resource_resolved:
if not silent:
time.sleep(0.1)
if current_resource:
prg = current_resource.get_progress()
percent = round(prg * 100.0, 1)
2024-10-07 08:44:18 +00:00
stat_str = f"{percent}% - {size_str(int(prg * current_resource.total_size))} of {size_str(current_resource.total_size)} - {size_str(speed, 'b')}ps"
2024-08-28 19:34:16 +00:00
if prg != 1.0:
2024-10-07 08:44:18 +00:00
print(f'\r \rTransferring file {syms[i]} {stat_str}', end=" ")
2024-08-28 19:34:16 +00:00
else:
2024-10-07 08:44:18 +00:00
print(f'\r \rTransfer complete {stat_str}', end=" ")
2023-09-18 20:22:44 +00:00
else:
2024-10-07 08:44:18 +00:00
print(f'\r \rWaiting for transfer to start {syms[i]} ', end=" ")
2023-09-18 20:22:44 +00:00
sys.stdout.flush()
i = (i+1)%len(syms)
if current_resource.status != RNS.Resource.COMPLETE:
if silent:
print("The transfer failed")
else:
print("\r \rThe transfer failed")
exit(1)
else:
if silent:
2024-10-07 08:44:18 +00:00
print(f"{file} fetched from {RNS.prettyhexrep(destination_hash)}")
2023-09-18 20:22:44 +00:00
else:
2024-08-28 19:21:38 +00:00
#print("\r \r"+str(file)+" fetched from "+RNS.prettyhexrep(destination_hash))
2024-10-07 08:44:18 +00:00
print(f"\n{file} fetched from {RNS.prettyhexrep(destination_hash)}")
2023-09-18 20:22:44 +00:00
link.teardown()
time.sleep(0.15)
2023-09-18 20:22:44 +00:00
exit(0)
link.teardown()
exit(0)
2023-09-18 14:36:58 +00:00
def send(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False):
2022-05-22 21:44:32 +00:00
global current_resource, resource_done, link, speed
from tempfile import TemporaryFile
targetloglevel = 3+verbosity-quietness
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination) != dest_len:
2024-10-07 08:44:18 +00:00
raise ValueError(f"Allowed destination length is invalid, must be {dest_len} hexadecimal characters ({dest_len // 2} bytes).")
2022-05-22 21:44:32 +00:00
try:
destination_hash = bytes.fromhex(destination)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
exit(1)
2024-10-07 08:52:43 +00:00
2022-05-22 21:44:32 +00:00
file_path = os.path.expanduser(file)
if not os.path.isfile(file_path):
print("File not found")
exit(1)
temp_file = TemporaryFile()
real_file = open(file_path, "rb")
filename_bytes = os.path.basename(file_path).encode("utf-8")
filename_len = len(filename_bytes)
if filename_len > 0xFFFF:
print("Filename exceeds max size, cannot send")
exit(1)
else:
print("Preparing file...", end=" ")
temp_file.write(filename_len.to_bytes(2, "big"))
temp_file.write(filename_bytes)
temp_file.write(real_file.read())
temp_file.seek(0)
2022-06-30 12:02:57 +00:00
print("\r \r", end="")
2022-05-22 21:44:32 +00:00
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
2024-10-07 08:44:18 +00:00
identity_path = f"{RNS.Reticulum.identitypath}/{APP_NAME}"
2022-05-22 21:44:32 +00:00
if os.path.isfile(identity_path):
identity = RNS.Identity.from_file(identity_path)
if identity == None:
2024-10-07 08:44:18 +00:00
RNS.log(f"Could not load identity for rncp. The identity file at \"{identity_path}\" may be corrupt or unreadable.", RNS.LOG_ERROR)
exit(2)
else:
identity = None
2022-05-22 21:44:32 +00:00
if identity == None:
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
identity = RNS.Identity()
identity.to_file(identity_path)
if not RNS.Transport.has_path(destination_hash):
RNS.Transport.request_path(destination_hash)
2023-09-18 14:36:58 +00:00
if silent:
2024-10-07 08:44:18 +00:00
print(f"Path to {RNS.prettyhexrep(destination_hash)} requested")
2023-09-18 14:36:58 +00:00
else:
2024-10-07 08:44:18 +00:00
print(f"Path to {RNS.prettyhexrep(destination_hash)} requested ", end=" ")
2022-05-22 21:44:32 +00:00
sys.stdout.flush()
i = 0
syms = "⢄⢂⢁⡁⡈⡐⡠"
estab_timeout = time.time()+timeout
while not RNS.Transport.has_path(destination_hash) and time.time() < estab_timeout:
2023-09-18 14:36:58 +00:00
if not silent:
time.sleep(0.1)
2024-10-07 08:44:18 +00:00
print(f"\b\b{syms[i]} ", end="")
2023-09-18 14:36:58 +00:00
sys.stdout.flush()
i = (i+1)%len(syms)
2022-05-22 21:44:32 +00:00
if not RNS.Transport.has_path(destination_hash):
2023-09-18 14:36:58 +00:00
if silent:
print("Path not found")
else:
print("\r \rPath not found")
2022-05-22 21:44:32 +00:00
exit(1)
else:
2023-09-18 14:36:58 +00:00
if silent:
2024-10-07 08:44:18 +00:00
print(f"Establishing link with {RNS.prettyhexrep(destination_hash)}")
2023-09-18 14:36:58 +00:00
else:
2024-10-07 08:44:18 +00:00
print(f'\r \rEstablishing link with {RNS.prettyhexrep(destination_hash)} ', end=" ")
2022-05-22 21:44:32 +00:00
receiver_identity = RNS.Identity.recall(destination_hash)
receiver_destination = RNS.Destination(
receiver_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
APP_NAME,
"receive"
)
link = RNS.Link(receiver_destination)
while link.status != RNS.Link.ACTIVE and time.time() < estab_timeout:
2023-09-18 14:36:58 +00:00
if not silent:
time.sleep(0.1)
2024-10-07 08:44:18 +00:00
print(f"\b\b{syms[i]} ", end="")
2023-09-18 14:36:58 +00:00
sys.stdout.flush()
i = (i+1)%len(syms)
2022-05-22 21:44:32 +00:00
2023-09-21 12:12:14 +00:00
if time.time() > estab_timeout:
2023-09-18 14:36:58 +00:00
if silent:
2024-10-07 08:44:18 +00:00
print(f"Link establishment with {RNS.prettyhexrep(destination_hash)} timed out")
2023-09-18 14:36:58 +00:00
else:
2024-10-07 08:44:18 +00:00
print(f'\r \rLink establishment with {RNS.prettyhexrep(destination_hash)} timed out')
2023-09-21 12:12:14 +00:00
exit(1)
elif not RNS.Transport.has_path(destination_hash):
if silent:
2024-10-07 08:44:18 +00:00
print(f"No path found to {RNS.prettyhexrep(destination_hash)}")
2023-09-21 12:12:14 +00:00
else:
2024-10-07 08:44:18 +00:00
print(f'\r \rNo path found to {RNS.prettyhexrep(destination_hash)}')
2022-05-22 21:44:32 +00:00
exit(1)
else:
2023-09-18 14:36:58 +00:00
if silent:
print("Advertising file resource...")
else:
print("\r \rAdvertising file resource ", end=" ")
2022-05-22 21:44:32 +00:00
link.identify(identity)
resource = RNS.Resource(temp_file, link, callback = sender_progress, progress_callback = sender_progress)
current_resource = resource
while resource.status < RNS.Resource.TRANSFERRING:
2023-09-18 14:36:58 +00:00
if not silent:
time.sleep(0.1)
2024-10-07 08:44:18 +00:00
print(f"\b\b{syms[i]} ", end="")
2023-09-18 14:36:58 +00:00
sys.stdout.flush()
i = (i+1)%len(syms)
2022-05-22 21:44:32 +00:00
2024-10-07 08:52:43 +00:00
2022-05-22 21:44:32 +00:00
if resource.status > RNS.Resource.COMPLETE:
2023-09-18 14:36:58 +00:00
if silent:
2024-10-07 08:44:18 +00:00
print(f"File was not accepted by {RNS.prettyhexrep(destination_hash)}")
2023-09-18 14:36:58 +00:00
else:
2024-10-07 08:44:18 +00:00
print(f'\r \rFile was not accepted by {RNS.prettyhexrep(destination_hash)}')
2022-05-22 21:44:32 +00:00
exit(1)
else:
2023-09-18 14:36:58 +00:00
if silent:
print("Transferring file...")
else:
print("\r \rTransferring file ", end=" ")
2022-05-22 21:44:32 +00:00
2024-08-28 19:34:16 +00:00
def progress_update(i, done=False):
2024-08-28 19:21:38 +00:00
time.sleep(0.1)
prg = current_resource.get_progress()
percent = round(prg * 100.0, 1)
2024-10-07 08:44:18 +00:00
stat_str = f"{percent}% - {size_str(int(prg * current_resource.total_size))} of {size_str(current_resource.total_size)} - {size_str(speed, 'b')}ps"
2024-08-28 19:34:16 +00:00
if not done:
2024-10-07 08:44:18 +00:00
print(f'\r \rTransferring file {syms[i]} {stat_str}', end=" ")
2024-08-28 19:34:16 +00:00
else:
2024-10-07 08:44:18 +00:00
print(f'\r \rTransfer complete {stat_str}', end=" ")
2024-08-28 19:21:38 +00:00
sys.stdout.flush()
i = (i+1)%len(syms)
return i
2022-05-22 21:44:32 +00:00
while not resource_done:
2023-09-18 14:36:58 +00:00
if not silent:
2024-08-28 19:21:38 +00:00
i = progress_update(i)
2022-05-22 21:44:32 +00:00
2024-08-28 19:34:16 +00:00
if not silent:
i = progress_update(i, done=True)
2022-05-22 21:44:32 +00:00
if current_resource.status != RNS.Resource.COMPLETE:
2023-09-18 14:36:58 +00:00
if silent:
print("The transfer failed")
else:
print("\r \rThe transfer failed")
2022-05-22 21:44:32 +00:00
exit(1)
else:
2023-09-18 14:36:58 +00:00
if silent:
2024-10-07 08:44:18 +00:00
print(f"{file_path} copied to {RNS.prettyhexrep(destination_hash)}")
2023-09-18 14:36:58 +00:00
else:
2024-08-28 19:21:38 +00:00
# print("\r \r"+str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
2024-10-07 08:44:18 +00:00
print(f"\n{file_path} copied to {RNS.prettyhexrep(destination_hash)}")
2022-05-22 21:44:32 +00:00
link.teardown()
time.sleep(0.25)
real_file.close()
temp_file.close()
exit(0)
def main():
try:
parser = argparse.ArgumentParser(description="Reticulum File Transfer Utility")
parser.add_argument("file", nargs="?", default=None, help="file to be transferred", type=str)
parser.add_argument("destination", nargs="?", default=None, help="hexadecimal hash of the receiver", type=str)
parser.add_argument("--config", metavar="path", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
parser.add_argument('-v', '--verbose', action='count', default=0, help="increase verbosity")
parser.add_argument('-q', '--quiet', action='count', default=0, help="decrease verbosity")
2023-09-18 14:36:58 +00:00
parser.add_argument("-S", '--silent', action='store_true', default=False, help="disable transfer progress output")
parser.add_argument("-l", '--listen', action='store_true', default=False, help="listen for incoming transfer requests")
parser.add_argument("-F", '--allow-fetch', action='store_true', default=False, help="allow authenticated clients to fetch files")
2023-09-18 20:40:29 +00:00
parser.add_argument("-f", '--fetch', action='store_true', default=False, help="fetch file from remote listener instead of sending")
parser.add_argument("-j", "--jail", metavar="path", action="store", default=None, help="restrict fetch requests to specified path", type=str)
2023-09-18 14:36:58 +00:00
parser.add_argument("-b", action='store', metavar="seconds", default=-1, help="announce interval, 0 to only announce at startup", type=int)
parser.add_argument('-a', metavar="allowed_hash", dest="allowed", action='append', help="allow this identity", type=str)
parser.add_argument('-n', '--no-auth', action='store_true', default=False, help="accept requests from anyone")
2023-09-18 14:36:58 +00:00
parser.add_argument('-p', '--print-identity', action='store_true', default=False, help="print identity and destination info and exit")
2022-05-22 21:44:32 +00:00
parser.add_argument("-w", action="store", metavar="seconds", type=float, help="sender timeout before giving up", default=RNS.Transport.PATH_REQUEST_TIMEOUT)
# parser.add_argument("--limit", action="store", metavar="files", type=float, help="maximum number of files to accept", default=None)
2024-10-07 08:44:18 +00:00
parser.add_argument("--version", action="version", version=f"rncp {__version__}")
2024-10-07 08:52:43 +00:00
2022-05-22 21:44:32 +00:00
args = parser.parse_args()
2023-09-18 14:36:58 +00:00
if args.listen or args.print_identity:
2023-09-18 20:22:44 +00:00
listen(
2022-05-22 21:44:32 +00:00
configdir = args.config,
verbosity=args.verbose,
quietness=args.quiet,
2022-05-24 18:13:54 +00:00
allowed = args.allowed,
fetch_allowed = args.allow_fetch,
jail = args.jail,
2022-05-24 18:13:54 +00:00
display_identity=args.print_identity,
2022-05-22 21:44:32 +00:00
# limit=args.limit,
disable_auth=args.no_auth,
2023-09-18 14:36:58 +00:00
announce=args.b,
2022-05-22 21:44:32 +00:00
)
2023-09-18 20:22:44 +00:00
elif args.fetch:
if args.destination != None and args.file != None:
fetch(
configdir = args.config,
verbosity = args.verbose,
quietness = args.quiet,
destination = args.destination,
file = args.file,
timeout = args.w,
silent = args.silent,
)
else:
print("")
parser.print_help()
print("")
2022-05-22 21:44:32 +00:00
elif args.destination != None and args.file != None:
send(
configdir = args.config,
verbosity = args.verbose,
quietness = args.quiet,
destination = args.destination,
file = args.file,
timeout = args.w,
2023-09-18 14:36:58 +00:00
silent = args.silent,
2022-05-22 21:44:32 +00:00
)
else:
print("")
parser.print_help()
print("")
except KeyboardInterrupt:
print("")
if resource != None:
resource.cancel()
if link != None:
link.teardown()
exit()
def size_str(num, suffix='B'):
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
if suffix == 'b':
num *= 8
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
for unit in units:
if abs(num) < 1000.0:
if unit == "":
2024-10-07 08:44:18 +00:00
return f"{num:.0f} {unit}{suffix}"
2022-05-22 21:44:32 +00:00
else:
2024-10-07 08:44:18 +00:00
return f"{num:.2f} {unit}{suffix}"
2022-05-22 21:44:32 +00:00
num /= 1000.0
2024-10-07 08:44:18 +00:00
return f"{num:.2f}{last_unit}{suffix}"
2022-05-22 21:44:32 +00:00
if __name__ == "__main__":
main()