2022-10-22 14:35:44 +02: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.
2022-10-22 20:12:54 +02:00
DEFFERED_JOBS_DELAY = 10
JOBS_INTERVAL = 5
2022-10-22 14:35:44 +02:00
import RNS
import LXMF
import argparse
2022-10-22 20:12:54 +02:00
import threading
2022-10-22 21:07:27 +02:00
import subprocess
import shlex
2022-10-22 14:35:44 +02:00
import time
import os
from LXMF . _version import __version__
2025-01-23 14:15:12 +01:00
from LXMF import APP_NAME
2022-10-22 14:35:44 +02:00
from RNS . vendor . configobj import ConfigObj
2022-10-22 20:12:54 +02:00
configpath = None
ignoredpath = None
identitypath = None
storagedir = None
2022-10-22 21:07:27 +02:00
lxmdir = None
2022-10-22 20:12:54 +02:00
targetloglevel = None
2022-10-22 14:35:44 +02:00
identity = None
lxmd_config = None
message_router = None
lxmf_destination = None
active_configuration = { }
2022-10-22 20:12:54 +02:00
last_peer_announce = None
last_node_announce = None
2022-10-22 14:35:44 +02:00
def create_default_config ( configpath ) :
lxmd_config = ConfigObj ( __default_lxmd_config__ . splitlines ( ) )
lxmd_config . filename = configpath
lxmd_config . write ( )
def apply_config ( ) :
2022-10-22 20:12:54 +02:00
global active_configuration , targetloglevel
try :
# Load peer settings
if " lxmf " in lxmd_config and " display_name " in lxmd_config [ " lxmf " ] :
active_configuration [ " display_name " ] = lxmd_config [ " lxmf " ] [ " display_name " ]
else :
active_configuration [ " display_name " ] = " Anonymous Peer "
if " lxmf " in lxmd_config and " announce_at_start " in lxmd_config [ " lxmf " ] :
active_configuration [ " peer_announce_at_start " ] = lxmd_config [ " lxmf " ] . as_bool ( " announce_at_start " )
else :
active_configuration [ " peer_announce_at_start " ] = False
if " lxmf " in lxmd_config and " announce_interval " in lxmd_config [ " lxmf " ] :
active_configuration [ " peer_announce_interval " ] = lxmd_config [ " lxmf " ] . as_int ( " announce_interval " ) * 60
else :
active_configuration [ " peer_announce_interval " ] = None
2024-03-01 22:37:54 +01:00
if " lxmf " in lxmd_config and " delivery_transfer_max_accepted_size " in lxmd_config [ " lxmf " ] :
active_configuration [ " delivery_transfer_max_accepted_size " ] = lxmd_config [ " lxmf " ] . as_float ( " delivery_transfer_max_accepted_size " )
if active_configuration [ " delivery_transfer_max_accepted_size " ] < 0.38 :
active_configuration [ " delivery_transfer_max_accepted_size " ] = 0.38
else :
2024-03-01 23:26:27 +01:00
active_configuration [ " delivery_transfer_max_accepted_size " ] = 1000
2022-10-22 20:12:54 +02:00
if " lxmf " in lxmd_config and " on_inbound " in lxmd_config [ " lxmf " ] :
active_configuration [ " on_inbound " ] = lxmd_config [ " lxmf " ] [ " on_inbound " ]
else :
active_configuration [ " on_inbound " ] = None
# Load propagation node settings
if " propagation " in lxmd_config and " enable_node " in lxmd_config [ " propagation " ] :
active_configuration [ " enable_propagation_node " ] = lxmd_config [ " propagation " ] . as_bool ( " enable_node " )
else :
active_configuration [ " enable_propagation_node " ] = False
2022-10-22 22:12:14 +02:00
if " propagation " in lxmd_config and " auth_required " in lxmd_config [ " propagation " ] :
active_configuration [ " auth_required " ] = lxmd_config [ " propagation " ] . as_bool ( " auth_required " )
else :
active_configuration [ " auth_required " ] = False
2022-10-22 20:12:54 +02:00
if " propagation " in lxmd_config and " announce_at_start " in lxmd_config [ " propagation " ] :
active_configuration [ " node_announce_at_start " ] = lxmd_config [ " propagation " ] . as_bool ( " announce_at_start " )
else :
active_configuration [ " node_announce_at_start " ] = False
2022-10-22 21:25:15 +02:00
if " propagation " in lxmd_config and " autopeer " in lxmd_config [ " propagation " ] :
active_configuration [ " autopeer " ] = lxmd_config [ " propagation " ] . as_bool ( " autopeer " )
else :
active_configuration [ " autopeer " ] = True
if " propagation " in lxmd_config and " autopeer_maxdepth " in lxmd_config [ " propagation " ] :
active_configuration [ " autopeer_maxdepth " ] = lxmd_config [ " propagation " ] . as_int ( " autopeer_maxdepth " )
else :
active_configuration [ " autopeer_maxdepth " ] = None
2022-10-22 20:12:54 +02:00
if " propagation " in lxmd_config and " announce_interval " in lxmd_config [ " propagation " ] :
active_configuration [ " node_announce_interval " ] = lxmd_config [ " propagation " ] . as_int ( " announce_interval " ) * 60
else :
active_configuration [ " node_announce_interval " ] = None
if " propagation " in lxmd_config and " message_storage_limit " in lxmd_config [ " propagation " ] :
active_configuration [ " message_storage_limit " ] = lxmd_config [ " propagation " ] . as_float ( " message_storage_limit " )
if active_configuration [ " message_storage_limit " ] < 0.005 :
active_configuration [ " message_storage_limit " ] = 0.005
else :
2025-01-24 00:26:47 +01:00
active_configuration [ " message_storage_limit " ] = 500
2022-10-22 20:12:54 +02:00
2024-03-01 22:37:54 +01:00
if " propagation " in lxmd_config and " propagation_transfer_max_accepted_size " in lxmd_config [ " propagation " ] :
active_configuration [ " propagation_transfer_max_accepted_size " ] = lxmd_config [ " propagation " ] . as_float ( " propagation_transfer_max_accepted_size " )
if active_configuration [ " propagation_transfer_max_accepted_size " ] < 0.38 :
active_configuration [ " propagation_transfer_max_accepted_size " ] = 0.38
else :
active_configuration [ " propagation_transfer_max_accepted_size " ] = 256
2022-10-22 20:12:54 +02:00
if " propagation " in lxmd_config and " prioritise_destinations " in lxmd_config [ " propagation " ] :
active_configuration [ " prioritised_lxmf_destinations " ] = lxmd_config [ " propagation " ] . as_list ( " prioritise_destinations " )
else :
active_configuration [ " prioritised_lxmf_destinations " ] = [ ]
2025-01-22 01:37:09 +01:00
if " propagation " in lxmd_config and " static_peers " in lxmd_config [ " propagation " ] :
static_peers = lxmd_config [ " propagation " ] . as_list ( " static_peers " )
active_configuration [ " static_peers " ] = [ ]
for static_peer in static_peers :
active_configuration [ " static_peers " ] . append ( bytes . fromhex ( static_peer ) )
else :
active_configuration [ " static_peers " ] = [ ]
if " propagation " in lxmd_config and " max_peers " in lxmd_config [ " propagation " ] :
active_configuration [ " max_peers " ] = lxmd_config [ " propagation " ] . as_int ( " max_peers " )
else :
active_configuration [ " max_peers " ] = None
if " propagation " in lxmd_config and " from_static_only " in lxmd_config [ " propagation " ] :
active_configuration [ " from_static_only " ] = lxmd_config [ " propagation " ] . as_bool ( " from_static_only " )
else :
active_configuration [ " from_static_only " ] = False
2022-10-22 20:12:54 +02:00
# Load various settings
if " logging " in lxmd_config and " loglevel " in lxmd_config [ " logging " ] :
targetloglevel = lxmd_config [ " logging " ] . as_int ( " loglevel " )
active_configuration [ " ignored_lxmf_destinations " ] = [ ]
if os . path . isfile ( ignoredpath ) :
try :
fh = open ( ignoredpath , " rb " )
ignored_input = fh . read ( )
fh . close ( )
ignored_hash_strs = ignored_input . splitlines ( )
for hash_str in ignored_hash_strs :
if len ( hash_str ) == RNS . Identity . TRUNCATED_HASHLENGTH / / 8 * 2 :
try :
ignored_hash = bytes . fromhex ( hash_str . decode ( " utf-8 " ) )
active_configuration [ " ignored_lxmf_destinations " ] . append ( ignored_hash )
except Exception as e :
2022-10-22 22:12:14 +02:00
RNS . log ( " Could not decode hash from: " + str ( hash_str ) , RNS . LOG_DEBUG )
2022-10-22 20:12:54 +02:00
RNS . log ( " The contained exception was: " + str ( e ) , RNS . LOG_DEBUG )
except Exception as e :
RNS . log ( " Error while loading list of ignored destinations: " + str ( e ) , RNS . LOG_ERROR )
2022-10-22 22:12:14 +02:00
active_configuration [ " allowed_identities " ] = [ ]
if os . path . isfile ( allowedpath ) :
try :
fh = open ( allowedpath , " rb " )
allowed_input = fh . read ( )
fh . close ( )
allowed_hash_strs = allowed_input . splitlines ( )
for hash_str in allowed_hash_strs :
if len ( hash_str ) == RNS . Identity . TRUNCATED_HASHLENGTH / / 8 * 2 :
try :
allowed_hash = bytes . fromhex ( hash_str . decode ( " utf-8 " ) )
active_configuration [ " allowed_identities " ] . append ( allowed_hash )
except Exception as e :
RNS . log ( " Could not decode hash from: " + str ( hash_str ) , RNS . LOG_DEBUG )
RNS . log ( " The contained exception was: " + str ( e ) , RNS . LOG_DEBUG )
except Exception as e :
RNS . log ( " Error while loading list of allowed identities: " + str ( e ) , RNS . LOG_ERROR )
2022-10-22 20:12:54 +02:00
except Exception as e :
RNS . log ( " Could not apply LXM Daemon configuration. The contained exception was: " + str ( e ) , RNS . LOG_ERROR )
raise e
exit ( 3 )
2022-10-22 14:35:44 +02:00
def lxmf_delivery ( lxm ) :
2022-10-22 21:07:27 +02:00
global active_configuration , lxmdir
try :
written_path = lxm . write_to_directory ( lxmdir )
RNS . log ( " Received " + str ( lxm ) + " written to " + str ( written_path ) , RNS . LOG_DEBUG )
if active_configuration [ " on_inbound " ] :
RNS . log ( " Calling external program to handle message " , RNS . LOG_DEBUG )
command = active_configuration [ " on_inbound " ]
processing_command = command + " \" " + written_path + " \" "
return_code = subprocess . call ( shlex . split ( processing_command ) , stdout = subprocess . DEVNULL , stderr = subprocess . DEVNULL )
else :
RNS . log ( " No action defined for inbound messages, ignoring " , RNS . LOG_DEBUG )
except Exception as e :
RNS . log ( " Error occurred while processing received message " + str ( lxm ) + " . The contained exception was: " + str ( e ) , RNS . LOG_ERROR )
2022-10-22 20:12:54 +02:00
def program_setup ( configdir = None , rnsconfigdir = None , run_pn = False , on_inbound = None , verbosity = 0 , quietness = 0 , service = False ) :
2022-10-22 22:12:14 +02:00
global configpath , ignoredpath , identitypath , allowedpath , storagedir , lxmdir
2022-10-22 20:12:54 +02:00
global lxmd_config , active_configuration , targetloglevel
global message_router , lxmf_destination
2022-10-22 14:35:44 +02:00
if service :
targetlogdest = RNS . LOG_FILE
targetloglevel = None
else :
targetlogdest = RNS . LOG_STDOUT
# Get configuration
if configdir == None :
if os . path . isdir ( " /etc/lxmd " ) and os . path . isfile ( " /etc/lxmd/config " ) :
configdir = " /etc/lxmd "
elif os . path . isdir ( RNS . Reticulum . userdir + " /.config/lxmd " ) and os . path . isfile ( Reticulum . userdir + " /.config/lxmd/config " ) :
configdir = RNS . Reticulum . userdir + " /.config/lxmd "
else :
configdir = RNS . Reticulum . userdir + " /.lxmd "
configpath = configdir + " /config "
2022-10-22 22:12:14 +02:00
ignoredpath = configdir + " /ignored "
allowedpath = configdir + " /allowed "
2022-10-22 14:35:44 +02:00
identitypath = configdir + " /identity "
storagedir = configdir + " /storage "
2022-10-22 21:07:27 +02:00
lxmdir = storagedir + " /messages "
2022-10-22 14:35:44 +02:00
if not os . path . isdir ( storagedir ) :
os . makedirs ( storagedir )
2022-10-22 21:07:27 +02:00
if not os . path . isdir ( lxmdir ) :
os . makedirs ( lxmdir )
2023-01-27 17:00:29 +01:00
if not os . path . isfile ( configpath ) :
RNS . log ( " Could not load config file, creating default configuration file... " )
create_default_config ( configpath )
RNS . log ( " Default config file created. Make any necessary changes in " + configpath + " and restart lxmd if needed. " )
time . sleep ( 1.5 )
2022-10-22 14:35:44 +02:00
if os . path . isfile ( configpath ) :
try :
lxmd_config = ConfigObj ( configpath )
except Exception as e :
RNS . log ( " Could not parse the configuration at " + configpath , RNS . LOG_ERROR )
RNS . log ( " Check your configuration file for errors! " , RNS . LOG_ERROR )
RNS . panic ( )
2023-01-27 17:00:29 +01:00
2022-10-22 14:35:44 +02:00
apply_config ( )
RNS . log ( " Configuration loaded from " + configpath , RNS . LOG_VERBOSE )
2023-01-27 17:00:29 +01:00
if targetloglevel == None :
targetloglevel = 3
if verbosity != 0 or quietness != 0 :
targetloglevel = targetloglevel + verbosity - quietness
2022-10-22 14:35:44 +02:00
# Start Reticulum
RNS . log ( " Substantiating Reticulum... " )
reticulum = RNS . Reticulum ( configdir = rnsconfigdir , loglevel = targetloglevel , logdest = targetlogdest )
# Generate or load primary identity
if os . path . isfile ( identitypath ) :
try :
identity = RNS . Identity . from_file ( identitypath )
if identity != None :
RNS . log ( " Loaded Primary Identity %s " % ( str ( identity ) ) )
else :
RNS . log ( " Could not load the Primary Identity from " + identitypath , RNS . LOG_ERROR )
2022-10-22 20:12:54 +02:00
exit ( 4 )
2022-10-22 14:35:44 +02:00
except Exception as e :
RNS . log ( " Could not load the Primary Identity from " + identitypath , RNS . LOG_ERROR )
RNS . log ( " The contained exception was: %s " % ( str ( e ) ) , RNS . LOG_ERROR )
exit ( 1 )
else :
try :
RNS . log ( " No Primary Identity file found, creating new... " )
identity = RNS . Identity ( )
identity . to_file ( identitypath )
RNS . log ( " Created new Primary Identity %s " % ( str ( identity ) ) )
except Exception as e :
RNS . log ( " Could not create and save a new Primary Identity " , RNS . LOG_ERROR )
RNS . log ( " The contained exception was: %s " % ( str ( e ) ) , RNS . LOG_ERROR )
exit ( 2 )
2022-10-22 22:12:14 +02:00
2022-10-22 14:35:44 +02:00
# Start LXMF
2022-10-22 21:25:15 +02:00
message_router = LXMF . LXMRouter (
identity = identity ,
storagepath = storagedir ,
autopeer = active_configuration [ " autopeer " ] ,
autopeer_maxdepth = active_configuration [ " autopeer_maxdepth " ] ,
2024-03-01 22:37:54 +01:00
propagation_limit = active_configuration [ " propagation_transfer_max_accepted_size " ] ,
delivery_limit = active_configuration [ " delivery_transfer_max_accepted_size " ] ,
2025-01-22 01:37:09 +01:00
max_peers = active_configuration [ " max_peers " ] ,
static_peers = active_configuration [ " static_peers " ] ,
from_static_only = active_configuration [ " from_static_only " ] )
2022-10-22 14:35:44 +02:00
message_router . register_delivery_callback ( lxmf_delivery )
for destination_hash in active_configuration [ " ignored_lxmf_destinations " ] :
message_router . ignore_destination ( destination_hash )
lxmf_destination = message_router . register_delivery_identity ( identity , display_name = active_configuration [ " display_name " ] )
RNS . Identity . remember (
packet_hash = None ,
destination_hash = lxmf_destination . hash ,
public_key = identity . get_public_key ( ) ,
app_data = None
)
2022-10-22 22:12:14 +02:00
# Set up authentication
if active_configuration [ " auth_required " ] :
message_router . set_authentication ( required = True )
2023-01-27 16:36:15 +01:00
if len ( active_configuration [ " allowed_identities " ] ) == 0 :
RNS . log ( " Clint authentication was enabled, but no identity hashes could be loaded from " + str ( allowedpath ) + " . Nobody will be able to sync messages from this propagation node. " , RNS . LOG_WARNING )
2022-10-22 22:12:14 +02:00
for identity_hash in active_configuration [ " allowed_identities " ] :
message_router . allow ( identity_hash )
2022-10-22 14:35:44 +02:00
RNS . log ( " LXMF Router ready to receive on " + RNS . prettyhexrep ( lxmf_destination . hash ) )
2022-10-22 20:12:54 +02:00
if run_pn or active_configuration [ " enable_propagation_node " ] :
2022-10-22 14:35:44 +02:00
message_router . set_message_storage_limit ( megabytes = active_configuration [ " message_storage_limit " ] )
for dest_str in active_configuration [ " prioritised_lxmf_destinations " ] :
try :
dest_hash = bytes . fromhex ( dest_str )
if len ( dest_hash ) == RNS . Reticulum . TRUNCATED_HASHLENGTH / / 8 :
message_router . prioritise ( dest_hash )
except Exception as e :
RNS . log ( " Cannot prioritise " + str ( dest_str ) + " , it is not a valid destination hash " , RNS . LOG_ERROR )
message_router . enable_propagation ( )
RNS . log ( " LXMF Propagation Node started on " + RNS . prettyhexrep ( message_router . propagation_destination . hash ) )
RNS . log ( " Started lxmd version {version} " . format ( version = __version__ ) , RNS . LOG_NOTICE )
2022-10-22 20:12:54 +02:00
threading . Thread ( target = deferred_start_jobs , daemon = True ) . start ( )
2022-10-22 14:35:44 +02:00
while True :
time . sleep ( 1 )
2022-10-22 20:12:54 +02:00
def jobs ( ) :
global active_configuration , last_peer_announce , last_node_announce
global message_router , lxmf_destination
while True :
try :
2023-01-27 16:36:15 +01:00
if " peer_announce_interval " in active_configuration and active_configuration [ " peer_announce_interval " ] != None :
if time . time ( ) > last_peer_announce + active_configuration [ " peer_announce_interval " ] :
2025-01-22 01:37:09 +01:00
RNS . log ( " Sending announce for LXMF delivery destination " , RNS . LOG_VERBOSE )
2023-01-27 16:36:15 +01:00
message_router . announce ( lxmf_destination . hash )
last_peer_announce = time . time ( )
if " node_announce_interval " in active_configuration and active_configuration [ " node_announce_interval " ] != None :
if time . time ( ) > last_node_announce + active_configuration [ " node_announce_interval " ] :
2025-01-22 01:37:09 +01:00
RNS . log ( " Sending announce for LXMF Propagation Node " , RNS . LOG_VERBOSE )
2023-01-27 16:36:15 +01:00
message_router . announce_propagation_node ( )
last_node_announce = time . time ( )
2022-10-22 20:12:54 +02:00
except Exception as e :
RNS . log ( " An error occurred while running periodic jobs. The contained exception was: " + str ( e ) , RNS . LOG_ERROR )
time . sleep ( JOBS_INTERVAL )
def deferred_start_jobs ( ) :
global active_configuration , last_peer_announce , last_node_announce
global message_router , lxmf_destination
time . sleep ( DEFFERED_JOBS_DELAY )
2025-01-22 01:37:09 +01:00
RNS . log ( " Running deferred start jobs " , RNS . LOG_DEBUG )
2022-10-22 20:12:54 +02:00
if active_configuration [ " peer_announce_at_start " ] :
RNS . log ( " Sending announce for LXMF delivery destination " , RNS . LOG_EXTREME )
message_router . announce ( lxmf_destination . hash )
if active_configuration [ " node_announce_at_start " ] :
RNS . log ( " Sending announce for LXMF Propagation Node " , RNS . LOG_EXTREME )
message_router . announce_propagation_node ( )
last_peer_announce = time . time ( )
last_node_announce = time . time ( )
threading . Thread ( target = jobs , daemon = True ) . start ( )
2025-01-24 14:05:12 +01:00
def query_status ( identity , timeout = 5 , exit_on_fail = False ) :
control_destination = RNS . Destination ( identity , RNS . Destination . OUT , RNS . Destination . SINGLE , APP_NAME , " propagation " , " control " )
timeout = time . time ( ) + timeout
def check_timeout ( ) :
if time . time ( ) > timeout :
if exit_on_fail :
2025-01-26 01:13:11 +01:00
RNS . log ( " Getting lxmd statistics timed out, exiting now " , RNS . LOG_ERROR )
2025-01-24 14:05:12 +01:00
exit ( 200 )
else :
return LXMF . LXMPeer . LXMPeer . ERROR_TIMEOUT
else :
time . sleep ( 0.1 )
if not RNS . Transport . has_path ( control_destination . hash ) :
RNS . Transport . request_path ( control_destination . hash )
while not RNS . Transport . has_path ( control_destination . hash ) :
2025-01-26 01:13:11 +01:00
tc = check_timeout ( )
if tc :
return tc
2025-01-24 14:05:12 +01:00
link = RNS . Link ( control_destination )
while not link . status == RNS . Link . ACTIVE :
2025-01-26 01:13:11 +01:00
tc = check_timeout ( )
if tc :
return tc
2025-01-24 14:05:12 +01:00
link . identify ( identity )
request_receipt = link . request ( LXMF . LXMRouter . STATS_GET_PATH , data = None , response_callback = None , failed_callback = None )
while not request_receipt . get_status ( ) == RNS . RequestReceipt . READY :
2025-01-26 01:13:11 +01:00
tc = check_timeout ( )
if tc :
return tc
2025-01-24 14:05:12 +01:00
return request_receipt . get_response ( )
2025-01-23 16:27:48 +01:00
def get_status ( configdir = None , rnsconfigdir = None , verbosity = 0 , quietness = 0 , timeout = 5 , show_status = False , show_peers = False , identity_path = None ) :
2025-01-23 14:15:12 +01:00
global configpath , identitypath , storagedir , lxmdir
global lxmd_config , active_configuration , targetloglevel
targetlogdest = RNS . LOG_STDOUT
2025-01-23 16:27:48 +01:00
if identity_path == None :
if configdir == None :
if os . path . isdir ( " /etc/lxmd " ) and os . path . isfile ( " /etc/lxmd/config " ) :
configdir = " /etc/lxmd "
elif os . path . isdir ( RNS . Reticulum . userdir + " /.config/lxmd " ) and os . path . isfile ( Reticulum . userdir + " /.config/lxmd/config " ) :
configdir = RNS . Reticulum . userdir + " /.config/lxmd "
else :
configdir = RNS . Reticulum . userdir + " /.lxmd "
configpath = configdir + " /config "
identitypath = configdir + " /identity "
identity = None
if not os . path . isdir ( configdir ) :
RNS . log ( " Specified configuration directory does not exist, exiting now " , RNS . LOG_ERROR )
exit ( 201 )
if not os . path . isfile ( identitypath ) :
RNS . log ( " Identity file not found in specified configuration directory, exiting now " , RNS . LOG_ERROR )
exit ( 202 )
2025-01-23 14:15:12 +01:00
else :
2025-01-23 16:27:48 +01:00
identity = RNS . Identity . from_file ( identitypath )
if identity == None :
RNS . log ( " Could not load the Primary Identity from " + identitypath , RNS . LOG_ERROR )
exit ( 4 )
2025-01-23 14:15:12 +01:00
else :
2025-01-23 16:27:48 +01:00
if not os . path . isfile ( identity_path ) :
RNS . log ( " Identity file not found in specified configuration directory, exiting now " , RNS . LOG_ERROR )
exit ( 202 )
else :
identity = RNS . Identity . from_file ( identity_path )
if identity == None :
RNS . log ( " Could not load the Primary Identity from " + identity_path , RNS . LOG_ERROR )
exit ( 4 )
2025-01-23 14:15:12 +01:00
if targetloglevel == None :
targetloglevel = 3
if verbosity != 0 or quietness != 0 :
targetloglevel = targetloglevel + verbosity - quietness
reticulum = RNS . Reticulum ( configdir = rnsconfigdir , loglevel = targetloglevel , logdest = targetlogdest )
2025-01-24 14:05:12 +01:00
response = query_status ( identity , timeout = timeout , exit_on_fail = True )
2025-01-23 14:15:12 +01:00
if response == LXMF . LXMPeer . LXMPeer . ERROR_NO_IDENTITY :
RNS . log ( " Remote received no identity " )
exit ( 203 )
if response == LXMF . LXMPeer . LXMPeer . ERROR_NO_ACCESS :
RNS . log ( " Access denied " )
exit ( 204 )
else :
2025-01-23 16:27:48 +01:00
s = response
2025-01-23 16:54:12 +01:00
mutil = round ( ( s [ " messagestore " ] [ " bytes " ] / s [ " messagestore " ] [ " limit " ] ) * 100 , 2 )
ms_util = f " { mutil } % "
2025-01-23 16:27:48 +01:00
if s [ " from_static_only " ] :
who_str = " static peers only "
else :
who_str = " all nodes "
available_peers = 0
unreachable_peers = 0
peered_incoming = 0
peered_outgoing = 0
peered_rx_bytes = 0
peered_tx_bytes = 0
for peer_id in s [ " peers " ] :
p = s [ " peers " ] [ peer_id ]
pm = p [ " messages " ]
peered_incoming + = pm [ " incoming " ]
peered_outgoing + = pm [ " outgoing " ]
peered_rx_bytes + = p [ " rx_bytes " ]
peered_tx_bytes + = p [ " tx_bytes " ]
if p [ " alive " ] :
available_peers + = 1
else :
unreachable_peers + = 1
total_incoming = peered_incoming + s [ " unpeered_propagation_incoming " ] + s [ " clients " ] [ " client_propagation_messages_received " ]
total_rx_bytes = peered_rx_bytes + s [ " unpeered_propagation_rx_bytes " ]
df = round ( peered_outgoing / total_incoming , 2 )
2025-01-23 16:54:12 +01:00
dhs = RNS . prettyhexrep ( s [ " destination_hash " ] ) ; uts = RNS . prettytime ( s [ " uptime " ] )
print ( f " \n LXMF Propagation Node running on { dhs } , uptime is { uts } " )
2025-01-23 16:27:48 +01:00
if show_status :
2025-01-23 16:54:12 +01:00
msb = RNS . prettysize ( s [ " messagestore " ] [ " bytes " ] ) ; msl = RNS . prettysize ( s [ " messagestore " ] [ " limit " ] )
ptl = RNS . prettysize ( s [ " propagation_limit " ] * 1000 ) ; uprx = RNS . prettysize ( s [ " unpeered_propagation_rx_bytes " ] )
mscnt = s [ " messagestore " ] [ " count " ] ; stp = s [ " total_peers " ] ; smp = s [ " max_peers " ] ; sdp = s [ " discovered_peers " ]
ssp = s [ " static_peers " ] ; cprr = s [ " clients " ] [ " client_propagation_messages_received " ]
cprs = s [ " clients " ] [ " client_propagation_messages_served " ] ; upi = s [ " unpeered_propagation_incoming " ]
print ( f " Messagestore contains { mscnt } messages, { msb } ( { ms_util } utilised of { msl } ) " )
print ( f " Accepting propagated messages from { who_str } , { ptl } per-transfer limit " )
2025-01-23 16:27:48 +01:00
print ( f " " )
2025-01-23 16:54:12 +01:00
print ( f " Peers : { stp } total (peer limit is { smp } ) " )
print ( f " { sdp } discovered, { ssp } static " )
2025-01-23 16:27:48 +01:00
print ( f " { available_peers } available, { unreachable_peers } unreachable " )
print ( f " " )
2025-01-24 00:21:37 +01:00
print ( f " Traffic : { total_incoming } messages received in total ( { RNS . prettysize ( total_rx_bytes ) } ) " )
2025-01-23 16:27:48 +01:00
print ( f " { peered_incoming } messages received from peered nodes ( { RNS . prettysize ( peered_rx_bytes ) } ) " )
2025-01-24 00:21:37 +01:00
print ( f " { upi } messages received from unpeered nodes ( { uprx } ) " )
2025-01-23 16:27:48 +01:00
print ( f " { peered_outgoing } messages transferred to peered nodes ( { RNS . prettysize ( peered_tx_bytes ) } ) " )
2025-01-24 00:21:37 +01:00
print ( f " { cprr } propagation messages received directly from clients " )
2025-01-23 17:43:24 +01:00
print ( f " { cprs } propagation messages served to clients " )
2025-01-23 16:27:48 +01:00
print ( f " Distribution factor is { df } " )
print ( f " " )
if show_peers :
2025-01-24 14:05:12 +01:00
if not show_status :
print ( " " )
2025-01-23 16:27:48 +01:00
for peer_id in s [ " peers " ] :
ind = " "
p = s [ " peers " ] [ peer_id ]
if p [ " type " ] == " static " :
t = " Static peer "
elif p [ " type " ] == " discovered " :
t = " Discovered peer "
else :
t = " Unknown peer "
a = " Available " if p [ " alive " ] == True else " Unreachable "
h = max ( time . time ( ) - p [ " last_heard " ] , 0 )
hops = p [ " network_distance " ]
2025-01-24 14:05:12 +01:00
hs = " hops unknown " if hops == RNS . Transport . PATHFINDER_M else f " { hops } hop away " if hops == 1 else f " { hops } hops away "
2025-01-23 16:27:48 +01:00
pm = p [ " messages " ]
if p [ " last_sync_attempt " ] != 0 :
2025-01-23 16:54:12 +01:00
lsa = p [ " last_sync_attempt " ]
ls = f " last synced { RNS . prettytime ( max ( time . time ( ) - lsa , 0 ) ) } ago "
2025-01-23 16:27:48 +01:00
else :
ls = " never synced "
2025-01-23 16:54:12 +01:00
sstr = RNS . prettyspeed ( p [ " str " ] ) ; sler = RNS . prettyspeed ( p [ " ler " ] ) ; stl = RNS . prettysize ( p [ " transfer_limit " ] * 1000 )
srxb = RNS . prettysize ( p [ " rx_bytes " ] ) ; stxb = RNS . prettysize ( p [ " tx_bytes " ] ) ; pmo = pm [ " offered " ] ; pmout = pm [ " outgoing " ]
pmi = pm [ " incoming " ] ; pmuh = pm [ " unhandled " ]
2025-01-23 16:27:48 +01:00
print ( f " { ind } { t } { RNS . prettyhexrep ( peer_id ) } " )
print ( f " { ind * 2 } Status : { a } , { hs } , last heard { RNS . prettytime ( h ) } ago " )
2025-01-23 16:54:12 +01:00
print ( f " { ind * 2 } Speeds : { sstr } STR, { sler } LER, { stl } transfer limit " )
print ( f " { ind * 2 } Messages : { pmo } offered, { pmout } outgoing, { pmi } incoming " )
print ( f " { ind * 2 } Traffic : { srxb } received, { stxb } sent " )
ms = " " if pm [ " unhandled " ] == 1 else " s "
print ( f " { ind * 2 } Sync state : { pmuh } unhandled message { ms } , { ls } " )
2025-01-23 16:27:48 +01:00
print ( " " )
2025-01-23 14:15:12 +01:00
2022-10-22 14:35:44 +02:00
def main ( ) :
try :
parser = argparse . ArgumentParser ( description = " Lightweight Extensible Messaging Daemon " )
parser . add_argument ( " --config " , action = " store " , default = None , help = " path to alternative lxmd config directory " , type = str )
parser . add_argument ( " --rnsconfig " , action = " store " , default = None , help = " path to alternative Reticulum config directory " , type = str )
parser . add_argument ( " -p " , " --propagation-node " , action = " store_true " , default = False , help = " run an LXMF Propagation Node " )
parser . add_argument ( " -i " , " --on-inbound " , action = " store " , metavar = " PATH " , default = None , help = " executable to run when a message is received " , type = str )
parser . add_argument ( " -v " , " --verbose " , action = " count " , default = 0 )
parser . add_argument ( " -q " , " --quiet " , action = " count " , default = 0 )
parser . add_argument ( " -s " , " --service " , action = " store_true " , default = False , help = " lxmd is running as a service and should log to file " )
2025-01-23 14:15:12 +01:00
parser . add_argument ( " --status " , action = " store_true " , default = False , help = " display node status " )
2025-01-23 16:27:48 +01:00
parser . add_argument ( " --peers " , action = " store_true " , default = False , help = " display peered nodes " )
2025-01-23 14:15:12 +01:00
parser . add_argument ( " --timeout " , action = " store " , default = 5 , help = " timeout in seconds for query operations " , type = float )
2025-01-23 16:27:48 +01:00
parser . add_argument ( " --identity " , action = " store " , default = None , help = " path to identity used for query request " , type = str )
2022-10-22 14:35:44 +02:00
parser . add_argument ( " --exampleconfig " , action = " store_true " , default = False , help = " print verbose configuration example to stdout and exit " )
parser . add_argument ( " --version " , action = " version " , version = " lxmd {version} " . format ( version = __version__ ) )
args = parser . parse_args ( )
if args . exampleconfig :
print ( __default_lxmd_config__ )
exit ( )
2025-01-23 16:27:48 +01:00
if args . status or args . peers :
2025-01-23 14:15:12 +01:00
get_status ( configdir = args . config ,
rnsconfigdir = args . rnsconfig ,
verbosity = args . verbose ,
quietness = args . quiet ,
2025-01-23 16:27:48 +01:00
timeout = args . timeout ,
show_status = args . status ,
show_peers = args . peers ,
identity_path = args . identity )
2025-01-23 14:15:12 +01:00
exit ( )
program_setup ( configdir = args . config ,
rnsconfigdir = args . rnsconfig ,
run_pn = args . propagation_node ,
on_inbound = args . on_inbound ,
verbosity = args . verbose ,
quietness = args . quiet ,
service = args . service )
2022-10-22 14:35:44 +02:00
except KeyboardInterrupt :
print ( " " )
exit ( )
__default_lxmd_config__ = """ # This is an example LXM Daemon config file.
# You should probably edit it to suit your
# intended usage.
[ propagation ]
# Whether to enable propagation node
2024-03-01 22:37:54 +01:00
2022-10-22 14:35:44 +02:00
enable_node = no
# Automatic announce interval in minutes.
# 6 hours by default.
2024-03-01 22:37:54 +01:00
2022-10-22 14:35:44 +02:00
announce_interval = 360
# Whether to announce when the node starts.
2024-03-01 22:37:54 +01:00
2022-10-22 14:35:44 +02:00
announce_at_start = yes
# Wheter to automatically peer with other
# propagation nodes on the network.
2024-03-01 22:37:54 +01:00
2022-10-22 14:35:44 +02:00
autopeer = yes
2022-10-22 21:25:15 +02:00
# The maximum peering depth (in hops) for
# automatically peered nodes.
2024-03-01 22:37:54 +01:00
2022-10-22 21:25:15 +02:00
autopeer_maxdepth = 4
2024-03-01 22:37:54 +01:00
# The maximum accepted transfer size per in-
# coming propagation transfer, in kilobytes.
# This also sets the upper limit for the size
# of single messages accepted onto this node.
#
# If a node wants to propagate a larger number
# of messages to this node, than what can fit
# within this limit, it will prioritise sending
# the smallest messages first, and try again
# with any remaining messages at a later point.
propagation_transfer_max_accepted_size = 256
2022-10-22 14:35:44 +02:00
# The maximum amount of storage to use for
# the LXMF Propagation Node message store,
# specified in megabytes. When this limit
# is reached, LXMF will periodically remove
# messages in its message store. By default,
# LXMF prioritises keeping messages that are
# new and small. Large and old messages will
# be removed first. This setting is optional
2025-01-24 00:26:47 +01:00
# and defaults to 500 megabytes.
2024-03-01 22:37:54 +01:00
2025-01-24 00:26:47 +01:00
# message_storage_limit = 500
2022-10-22 14:35:44 +02:00
# You can tell the LXMF message router to
# prioritise storage for one or more
# destinations. If the message store reaches
# the specified limit, LXMF will prioritise
# keeping messages for destinations specified
# with this option. This setting is optional,
# and generally you do not need to use it.
2024-03-01 22:37:54 +01:00
2022-10-22 14:35:44 +02:00
# prioritise_destinations = 41d20c727598a3fbbdf9106133a3a0ed, d924b81822ca24e68e2effea99bcb8cf
2022-10-22 22:12:14 +02:00
# By default, any destination is allowed to
# connect and download messages, but you can
# optionally restrict this. If you enable
# authentication, you must provide a list of
# allowed identity hashes in the a file named
# "allowed" in the lxmd config directory.
2024-03-01 22:37:54 +01:00
2022-10-22 22:12:14 +02:00
auth_required = no
2022-10-22 14:35:44 +02:00
[ lxmf ]
# The LXM Daemon will create an LXMF destination
# that it can receive messages on. This option sets
# the announced display name for this destination.
2024-03-01 22:37:54 +01:00
2022-10-22 14:35:44 +02:00
display_name = Anonymous Peer
# It is possible to announce the internal LXMF
# destination when the LXM Daemon starts up.
2024-03-01 22:37:54 +01:00
2022-10-22 14:35:44 +02:00
announce_at_start = no
2022-10-22 20:12:54 +02:00
# You can also announce the delivery destination
# at a specified interval. This is not enabled by
# default.
2024-03-01 22:37:54 +01:00
2022-10-22 20:12:54 +02:00
# announce_interval = 360
2024-03-01 22:37:54 +01:00
# The maximum accepted unpacked size for mes-
# sages received directly from other peers,
# specified in kilobytes. Messages larger than
# this will be rejected before the transfer
# begins.
2024-03-01 23:26:27 +01:00
delivery_transfer_max_accepted_size = 1000
2024-03-01 22:37:54 +01:00
2022-10-22 20:12:54 +02:00
# You can configure an external program to be run
# every time a message is received. The program
# will receive as an argument the full path to the
# message saved as a file. The example below will
# simply result in the message getting deleted as
# soon as it has been received.
2024-03-01 22:37:54 +01:00
2022-10-22 22:12:14 +02:00
# on_inbound = rm
2022-10-22 20:12:54 +02:00
2022-10-22 14:35:44 +02:00
[ logging ]
# Valid log levels are 0 through 7:
# 0: Log only critical information
# 1: Log errors and lower log levels
# 2: Log warnings and lower log levels
# 3: Log notices and lower log levels
# 4: Log info and lower (this is the default)
# 5: Verbose logging
# 6: Debug logging
# 7: Extreme logging
2024-03-01 22:37:54 +01:00
2022-10-22 14:35:44 +02:00
loglevel = 4
"""
if __name__ == " __main__ " :
main ( )