From 81084f17f80d5850731fff0e18e6b42c9ebaf6c0 Mon Sep 17 00:00:00 2001 From: SebastianObi Date: Wed, 7 Dec 2022 17:05:23 +0100 Subject: [PATCH 1/2] LXMF class improved, title and fields transport of distribution group and echo, added lxmf_provisioning server/script --- lxmf_bridge_mqtt/lxmf_bridge_mqtt.py | 26 +- lxmf_chatbot/lxmf_chatbot.py | 26 +- lxmf_cmd/lxmf_cmd.py | 26 +- .../lxmf_distribution_group.py | 102 +- .../lxmf_distribution_group_minimal.py | 44 +- lxmf_echo/lxmf_echo.py | 43 +- lxmf_ping/lxmf_ping.py | 26 +- lxmf_provisioning/CHANGELOG.md | 0 lxmf_provisioning/README.md | 2 + lxmf_provisioning/lxmf_provisioning.py | 1122 +++++++++++++++++ lxmf_terminal/lxmf_terminal.py | 26 +- 11 files changed, 1378 insertions(+), 65 deletions(-) create mode 100644 lxmf_provisioning/CHANGELOG.md create mode 100644 lxmf_provisioning/README.md create mode 100755 lxmf_provisioning/lxmf_provisioning.py diff --git a/lxmf_bridge_mqtt/lxmf_bridge_mqtt.py b/lxmf_bridge_mqtt/lxmf_bridge_mqtt.py index 2d66a00..c173980 100755 --- a/lxmf_bridge_mqtt/lxmf_bridge_mqtt.py +++ b/lxmf_bridge_mqtt/lxmf_bridge_mqtt.py @@ -99,7 +99,7 @@ class lxmf_connection: message_notification_failed_callback = None - def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): + def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): self.storage_path = storage_path self.identity_file = identity_file @@ -111,6 +111,7 @@ class lxmf_connection: self.aspect_filter = self.destination_name + "." + self.destination_type self.display_name = display_name + self.announce_data = announce_data self.send_delay = int(send_delay) @@ -166,9 +167,11 @@ class lxmf_connection: self.message_router = LXMF.LXMRouter(identity=self.identity, storagepath=self.storage_path) - self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) - - self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + if self.destination_name == "lxmf" and self.destination_type == "delivery": + self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) + self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + else: + self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, self.destination_name, self.destination_type) if self.display_name == "": self.display_name = RNS.prettyhexrep(self.destination_hash()) @@ -368,8 +371,19 @@ class lxmf_connection: def announce_now(self, app_data=None): if app_data: - self.destination.announce(app_data.encode("utf-8")) - log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ":" + app_data, LOG_DEBUG) + if isinstance(app_data, str): + self.destination.announce(app_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG) + else: + self.destination.announce(app_data) + log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) + elif self.announce_data: + if isinstance(self.announce_data, str): + self.destination.announce(self.announce_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG) + else: + self.destination.announce(self.announce_data) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) else: self.destination.announce() log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ": " + self.display_name, LOG_DEBUG) diff --git a/lxmf_chatbot/lxmf_chatbot.py b/lxmf_chatbot/lxmf_chatbot.py index 6177142..b47ba5d 100755 --- a/lxmf_chatbot/lxmf_chatbot.py +++ b/lxmf_chatbot/lxmf_chatbot.py @@ -97,7 +97,7 @@ class lxmf_connection: message_notification_failed_callback = None - def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): + def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): self.storage_path = storage_path self.identity_file = identity_file @@ -109,6 +109,7 @@ class lxmf_connection: self.aspect_filter = self.destination_name + "." + self.destination_type self.display_name = display_name + self.announce_data = announce_data self.send_delay = int(send_delay) @@ -164,9 +165,11 @@ class lxmf_connection: self.message_router = LXMF.LXMRouter(identity=self.identity, storagepath=self.storage_path) - self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) - - self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + if self.destination_name == "lxmf" and self.destination_type == "delivery": + self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) + self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + else: + self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, self.destination_name, self.destination_type) if self.display_name == "": self.display_name = RNS.prettyhexrep(self.destination_hash()) @@ -366,8 +369,19 @@ class lxmf_connection: def announce_now(self, app_data=None): if app_data: - self.destination.announce(app_data.encode("utf-8")) - log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ":" + app_data, LOG_DEBUG) + if isinstance(app_data, str): + self.destination.announce(app_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG) + else: + self.destination.announce(app_data) + log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) + elif self.announce_data: + if isinstance(self.announce_data, str): + self.destination.announce(self.announce_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG) + else: + self.destination.announce(self.announce_data) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) else: self.destination.announce() log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ": " + self.display_name, LOG_DEBUG) diff --git a/lxmf_cmd/lxmf_cmd.py b/lxmf_cmd/lxmf_cmd.py index 424e914..8962174 100755 --- a/lxmf_cmd/lxmf_cmd.py +++ b/lxmf_cmd/lxmf_cmd.py @@ -95,7 +95,7 @@ class lxmf_connection: message_notification_failed_callback = None - def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): + def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): self.storage_path = storage_path self.identity_file = identity_file @@ -107,6 +107,7 @@ class lxmf_connection: self.aspect_filter = self.destination_name + "." + self.destination_type self.display_name = display_name + self.announce_data = announce_data self.send_delay = int(send_delay) @@ -162,9 +163,11 @@ class lxmf_connection: self.message_router = LXMF.LXMRouter(identity=self.identity, storagepath=self.storage_path) - self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) - - self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + if self.destination_name == "lxmf" and self.destination_type == "delivery": + self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) + self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + else: + self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, self.destination_name, self.destination_type) if self.display_name == "": self.display_name = RNS.prettyhexrep(self.destination_hash()) @@ -364,8 +367,19 @@ class lxmf_connection: def announce_now(self, app_data=None): if app_data: - self.destination.announce(app_data.encode("utf-8")) - log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ":" + app_data, LOG_DEBUG) + if isinstance(app_data, str): + self.destination.announce(app_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG) + else: + self.destination.announce(app_data) + log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) + elif self.announce_data: + if isinstance(self.announce_data, str): + self.destination.announce(self.announce_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG) + else: + self.destination.announce(self.announce_data) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) else: self.destination.announce() log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ": " + self.display_name, LOG_DEBUG) diff --git a/lxmf_distribution_group/lxmf_distribution_group.py b/lxmf_distribution_group/lxmf_distribution_group.py index 1cd719d..07c7c28 100755 --- a/lxmf_distribution_group/lxmf_distribution_group.py +++ b/lxmf_distribution_group/lxmf_distribution_group.py @@ -106,7 +106,7 @@ class lxmf_connection: message_notification_failed_callback = None - def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): + def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): self.storage_path = storage_path self.identity_file = identity_file @@ -118,6 +118,7 @@ class lxmf_connection: self.aspect_filter = self.destination_name + "." + self.destination_type self.display_name = display_name + self.announce_data = announce_data self.send_delay = int(send_delay) @@ -173,9 +174,11 @@ class lxmf_connection: self.message_router = LXMF.LXMRouter(identity=self.identity, storagepath=self.storage_path) - self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) - - self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + if self.destination_name == "lxmf" and self.destination_type == "delivery": + self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) + self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + else: + self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, self.destination_name, self.destination_type) if self.display_name == "": self.display_name = RNS.prettyhexrep(self.destination_hash()) @@ -375,8 +378,19 @@ class lxmf_connection: def announce_now(self, app_data=None): if app_data: - self.destination.announce(app_data.encode("utf-8")) - log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ":" + app_data, LOG_DEBUG) + if isinstance(app_data, str): + self.destination.announce(app_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG) + else: + self.destination.announce(app_data) + log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) + elif self.announce_data: + if isinstance(self.announce_data, str): + self.destination.announce(self.announce_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG) + else: + self.destination.announce(self.announce_data) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) else: self.destination.announce() log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ": " + self.display_name, LOG_DEBUG) @@ -655,11 +669,19 @@ class rns_connection: def announce_now(self, app_data=None): if app_data: - self.destination.announce(app_data.encode("utf-8")) - log("RNS - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ":" + app_data, LOG_DEBUG) + if isinstance(app_data, str): + self.destination.announce(app_data.encode("utf-8")) + log("RNS - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG) + else: + self.destination.announce(app_data) + log("RNS - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) else: - self.destination.announce(self.announce_data.encode("utf-8")) - log("RNS - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG) + if isinstance(self.announce_data, str): + self.destination.announce(self.announce_data.encode("utf-8")) + log("RNS - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG) + else: + self.destination.announce(self.announce_data) + log("RNS - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) ############################################################################################################## @@ -731,11 +753,16 @@ def lxmf_message_received_callback(message): content = message.content.decode('utf-8') content = content.strip() - if content == "": + if CONFIG["message"].getboolean("fields") and message.fields: + pass + elif content == "": return - title = message.title.decode('utf-8') - title = title.strip() + if CONFIG["message"].getboolean("title"): + title = message.title.decode('utf-8') + title = title.strip() + else: + title = "" fields = message.fields @@ -805,6 +832,15 @@ def lxmf_message_received_callback(message): else: timestamp = time.time() + if CONFIG["message"].getboolean("fields"): + if message.fields: + fields = message.fields + else: + fields = {} + else: + fields = {} + fields["type"] = CONFIG["lxmf"]["destination_type_conv"] + if CONFIG["statistic"].getboolean("enabled") and CONFIG["statistic"].getboolean("cluster"): statistic("add", "cluster_in_" + message.desired_method_str) @@ -813,7 +849,7 @@ def lxmf_message_received_callback(message): if "receive_cluster" in config_get(CONFIG, "rights", section).split(","): for (key, val) in DATA.items(section): if key != source_hash: - LXMF_CONNECTION.send(key, content, title, {"type": CONFIG["lxmf"]["destination_type_conv"]}, timestamp, "cluster_send") + LXMF_CONNECTION.send(key, content, title, fields, timestamp, "cluster_send") elif fields["m_t"] == "pin": delimiter = CONFIG["interface"]["delimiter_output"] @@ -840,7 +876,7 @@ def lxmf_message_received_callback(message): if "receive_cluster_pin_add" in config_get(CONFIG, "rights", section).split(","): for (key, val) in DATA.items(section): if key != source_hash: - LXMF_CONNECTION.send(key, content_group, "", {"type": CONFIG["lxmf"]["destination_type_conv"]}, None, "cluster_send") + LXMF_CONNECTION.send(key, content_group, "", fields, None, "cluster_send") if CONFIG["main"].getboolean("auto_save_data"): DATA.remove_option("main", "unsaved") @@ -849,7 +885,7 @@ def lxmf_message_received_callback(message): else: DATA["main"]["unsaved"] = "True" - return + return if source_right == "" and DATA["main"].getboolean("auto_add_user"): if CONFIG["lxmf"].getboolean("signature_validated_new") and not message.signature_validated: @@ -1053,7 +1089,13 @@ def lxmf_message_received_callback(message): if search != "": content = re.sub(search, config_get(CONFIG, "message", "cluster_send_regex_replace"), content) - fields = defaultdict(dict) + if CONFIG["message"].getboolean("fields"): + if message.fields: + fields = message.fields + else: + fields = {} + else: + fields = {} fields["c_n"] = CONFIG["cluster"]["name"] fields["c_t"] = CONFIG["cluster"]["type"] @@ -1087,11 +1129,20 @@ def lxmf_message_received_callback(message): if destination in config_get(CONFIG, "cluster", "display_name", "", lng_key).split("/"): cluster_loop = True + if CONFIG["message"].getboolean("fields"): + if message.fields: + fields = message.fields + else: + fields = {} + else: + fields = {} + fields["type"] = CONFIG["lxmf"]["destination_type_conv"] + for section in sections: if "receive_cluster_send" in config_get(CONFIG, "rights", section).split(",") or (cluster_loop and "receive_cluster_loop" in config_get(CONFIG, "rights", section).split(",")): for (key, val) in DATA.items(section): if key != source_hash: - LXMF_CONNECTION.send(key, content, title, {"type": CONFIG["lxmf"]["destination_type_conv"]}, timestamp, "local_send") + LXMF_CONNECTION.send(key, content, title, fields, timestamp, "local_send") return @@ -1136,6 +1187,15 @@ def lxmf_message_received_callback(message): content = content_prefix + content + content_suffix + if CONFIG["message"].getboolean("fields"): + if message.fields: + fields = message.fields + else: + fields = {} + else: + fields = {} + fields["type"] = CONFIG["lxmf"]["destination_type_conv"] + if config_get(CONFIG, "message", "timestamp", "", lng_key) == "client": timestamp = message.timestamp else: @@ -1153,7 +1213,7 @@ def lxmf_message_received_callback(message): if "receive_local" in config_get(CONFIG, "rights", section).split(","): for (key, val) in DATA.items(section): if key != source_hash: - LXMF_CONNECTION.send(key, content, title, {"type": CONFIG["lxmf"]["destination_type_conv"]}, timestamp, "local_send") + LXMF_CONNECTION.send(key, content, title, fields, timestamp, "local_send") return else: log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " 'send' not allowed", LOG_DEBUG) @@ -3861,6 +3921,10 @@ pin_id = %%y%%m%%d-%%H%%M%%S # Define which message timestamp should be used. timestamp = client #client/server +# Use title/fields. +title = Yes +fields = Yes + diff --git a/lxmf_distribution_group_minimal/lxmf_distribution_group_minimal.py b/lxmf_distribution_group_minimal/lxmf_distribution_group_minimal.py index ef305a9..76a1265 100755 --- a/lxmf_distribution_group_minimal/lxmf_distribution_group_minimal.py +++ b/lxmf_distribution_group_minimal/lxmf_distribution_group_minimal.py @@ -93,7 +93,7 @@ class lxmf_connection: message_notification_failed_callback = None - def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): + def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): self.storage_path = storage_path self.identity_file = identity_file @@ -105,6 +105,7 @@ class lxmf_connection: self.aspect_filter = self.destination_name + "." + self.destination_type self.display_name = display_name + self.announce_data = announce_data self.send_delay = int(send_delay) @@ -160,9 +161,11 @@ class lxmf_connection: self.message_router = LXMF.LXMRouter(identity=self.identity, storagepath=self.storage_path) - self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) - - self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + if self.destination_name == "lxmf" and self.destination_type == "delivery": + self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) + self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + else: + self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, self.destination_name, self.destination_type) if self.display_name == "": self.display_name = RNS.prettyhexrep(self.destination_hash()) @@ -362,8 +365,19 @@ class lxmf_connection: def announce_now(self, app_data=None): if app_data: - self.destination.announce(app_data.encode("utf-8")) - log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ":" + app_data, LOG_DEBUG) + if isinstance(app_data, str): + self.destination.announce(app_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG) + else: + self.destination.announce(app_data) + log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) + elif self.announce_data: + if isinstance(self.announce_data, str): + self.destination.announce(self.announce_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG) + else: + self.destination.announce(self.announce_data) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) else: self.destination.announce() log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ": " + self.display_name, LOG_DEBUG) @@ -538,8 +552,16 @@ def lxmf_message_received_callback(message): if content == "": return - title = message.title.decode('utf-8') - title = title.strip() + if CONFIG["message"].getboolean("title"): + title = message.title.decode('utf-8') + title = title.strip() + else: + title = "" + + if CONFIG["message"].getboolean("fields"): + fields = message.fields + else: + fields = None source_hash = RNS.hexrep(message.source_hash, False) source_name = "" @@ -617,7 +639,7 @@ def lxmf_message_received_callback(message): if "receive" in section: for (key, val) in DATA.items(section): if key != source_hash: - LXMF_CONNECTION.send(key, content, title, None, timestamp) + LXMF_CONNECTION.send(key, content, title, fields, timestamp) return else: log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " 'send' not allowed", LOG_DEBUG) @@ -1267,6 +1289,10 @@ send_length_max = 0 #0=any length # Define which message timestamp should be used. timestamp = client #client/server + +# Use title/fields. +title = Yes +fields = Yes ''' diff --git a/lxmf_echo/lxmf_echo.py b/lxmf_echo/lxmf_echo.py index 434ca81..82d9ce9 100755 --- a/lxmf_echo/lxmf_echo.py +++ b/lxmf_echo/lxmf_echo.py @@ -91,7 +91,7 @@ class lxmf_connection: message_notification_failed_callback = None - def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): + def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): self.storage_path = storage_path self.identity_file = identity_file @@ -103,6 +103,7 @@ class lxmf_connection: self.aspect_filter = self.destination_name + "." + self.destination_type self.display_name = display_name + self.announce_data = announce_data self.send_delay = int(send_delay) @@ -158,9 +159,11 @@ class lxmf_connection: self.message_router = LXMF.LXMRouter(identity=self.identity, storagepath=self.storage_path) - self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) - - self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + if self.destination_name == "lxmf" and self.destination_type == "delivery": + self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) + self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + else: + self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, self.destination_name, self.destination_type) if self.display_name == "": self.display_name = RNS.prettyhexrep(self.destination_hash()) @@ -360,8 +363,19 @@ class lxmf_connection: def announce_now(self, app_data=None): if app_data: - self.destination.announce(app_data.encode("utf-8")) - log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ":" + app_data, LOG_DEBUG) + if isinstance(app_data, str): + self.destination.announce(app_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG) + else: + self.destination.announce(app_data) + log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) + elif self.announce_data: + if isinstance(self.announce_data, str): + self.destination.announce(self.announce_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG) + else: + self.destination.announce(self.announce_data) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) else: self.destination.announce() log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ": " + self.display_name, LOG_DEBUG) @@ -581,7 +595,18 @@ def lxmf_message_received_callback(message): content = content_prefix + content + content_suffix - LXMF_CONNECTION.send(message.source_hash, content, message.title.decode('utf-8').strip()) + if CONFIG["message"].getboolean("title"): + title = message.title.decode('utf-8') + title = title.strip() + else: + title = "" + + if CONFIG["message"].getboolean("fields"): + fields = message.fields + else: + fields = None + + LXMF_CONNECTION.send(message.source_hash, content, title, fields) else: log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " not allowed", LOG_DEBUG) return @@ -1106,6 +1131,10 @@ send_regex_replace = send_length_min = 0 #0=any length send_length_max = 0 #0=any length +# Use title/fields. +title = Yes +fields = Yes + diff --git a/lxmf_ping/lxmf_ping.py b/lxmf_ping/lxmf_ping.py index 2f8ba70..ad66e73 100755 --- a/lxmf_ping/lxmf_ping.py +++ b/lxmf_ping/lxmf_ping.py @@ -92,7 +92,7 @@ class lxmf_connection: message_notification_failed_callback = None - def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): + def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): self.storage_path = storage_path self.identity_file = identity_file @@ -104,6 +104,7 @@ class lxmf_connection: self.aspect_filter = self.destination_name + "." + self.destination_type self.display_name = display_name + self.announce_data = announce_data self.send_delay = int(send_delay) @@ -159,9 +160,11 @@ class lxmf_connection: self.message_router = LXMF.LXMRouter(identity=self.identity, storagepath=self.storage_path) - self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) - - self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + if self.destination_name == "lxmf" and self.destination_type == "delivery": + self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) + self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + else: + self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, self.destination_name, self.destination_type) if self.display_name == "": self.display_name = RNS.prettyhexrep(self.destination_hash()) @@ -361,8 +364,19 @@ class lxmf_connection: def announce_now(self, app_data=None): if app_data: - self.destination.announce(app_data.encode("utf-8")) - log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ":" + app_data, LOG_DEBUG) + if isinstance(app_data, str): + self.destination.announce(app_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG) + else: + self.destination.announce(app_data) + log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) + elif self.announce_data: + if isinstance(self.announce_data, str): + self.destination.announce(self.announce_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG) + else: + self.destination.announce(self.announce_data) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) else: self.destination.announce() log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ": " + self.display_name, LOG_DEBUG) diff --git a/lxmf_provisioning/CHANGELOG.md b/lxmf_provisioning/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/lxmf_provisioning/README.md b/lxmf_provisioning/README.md new file mode 100644 index 0000000..10b69ec --- /dev/null +++ b/lxmf_provisioning/README.md @@ -0,0 +1,2 @@ +# lxmf_provisioning +For more information, see the configuration options (at the end of the program files). Everything else is briefly documented there. After the first start this configuration will be created as default config in the corresponding file. diff --git a/lxmf_provisioning/lxmf_provisioning.py b/lxmf_provisioning/lxmf_provisioning.py new file mode 100755 index 0000000..b5be2ab --- /dev/null +++ b/lxmf_provisioning/lxmf_provisioning.py @@ -0,0 +1,1122 @@ +#!/usr/bin/env python3 +############################################################################################################## +# +# Copyright (c) 2022 Sebastian Obele / obele.eu +# +# 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. +# +# This software uses the following software-parts: +# Reticulum, LXMF, NomadNet / Copyright (c) 2016-2022 Mark Qvist / unsigned.io / MIT License +# +############################################################################################################## + + +############################################################################################################## +# Include + + +#### System #### +import sys +import os +import time +import argparse + +#### Config #### +import configparser + +#### JSON #### +import json +import pickle + +#### String #### +import string + +#### Process #### +import signal +import threading + +#### Reticulum, LXMF #### +# Install: pip3 install rns lxmf +# Source: https://markqvist.github.io +import RNS +import LXMF +import RNS.vendor.umsgpack as umsgpack + +#### PostgreSQL #### +# Install: pip3 install psycopg2 +# Install: pip3 install psycopg2-binary +# Source: https://pypi.org/project/psycopg2/ +import psycopg2 + + +############################################################################################################## +# Globals + + +#### Global Variables - Configuration #### +NAME = "LXMF Provisioning Server" +DESCRIPTION = "" +VERSION = "0.0.1 (2022-12-05)" +COPYRIGHT = "(c) 2022 Sebastian Obele / obele.eu" +PATH = os.path.expanduser("~") + "/." + os.path.splitext(os.path.basename(__file__))[0] +PATH_RNS = None + + + + +#### Global Variables - System (Not changeable) #### +CACHE = [] +CONFIG = None +RNS_CONNECTION = None +LXMF_CONNECTION = None +DB_CONNECTION = None + + +############################################################################################################## +# LXMF Class + + +class lxmf_connection: + message_received_callback = None + message_notification_callback = None + message_notification_success_callback = None + message_notification_failed_callback = None + + + def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): + self.storage_path = storage_path + + self.identity_file = identity_file + + self.identity = identity + + self.destination_name = destination_name + self.destination_type = destination_type + self.aspect_filter = self.destination_name + "." + self.destination_type + + self.display_name = display_name + self.announce_data = announce_data + + self.send_delay = int(send_delay) + + if desired_method == "propagated" or desired_method == "PROPAGATED": + self.desired_method_direct = False + else: + self.desired_method_direct = True + self.propagation_node = propagation_node + self.try_propagation_on_fail = try_propagation_on_fail + + self.announce_startup = announce_startup + self.announce_startup_delay = int(announce_startup_delay) + + self.announce_periodic = announce_periodic + self.announce_periodic_interval = int(announce_periodic_interval) + + self.sync_startup = sync_startup + self.sync_startup_delay = int(sync_startup_delay) + self.sync_limit = int(sync_limit) + self.sync_periodic = sync_periodic + self.sync_periodic_interval = int(sync_periodic_interval) + + if not os.path.isdir(self.storage_path): + os.makedirs(self.storage_path) + log("LXMF - Storage path was created", LOG_NOTICE) + log("LXMF - Storage path: " + self.storage_path, LOG_INFO) + + if self.identity: + log("LXMF - Using existing Primary Identity %s" % (str(self.identity))) + else: + if not self.identity_file: + self.identity_file = "identity" + self.identity_path = self.storage_path + "/" + self.identity_file + if os.path.isfile(self.identity_path): + try: + self.identity = RNS.Identity.from_file(self.identity_path) + if self.identity != None: + log("LXMF - Loaded Primary Identity %s from %s" % (str(self.identity), self.identity_path)) + else: + log("LXMF - Could not load the Primary Identity from "+self.identity_path, LOG_ERROR) + except Exception as e: + log("LXMF - Could not load the Primary Identity from "+self.identity_path, LOG_ERROR) + log("LXMF - The contained exception was: %s" % (str(e)), LOG_ERROR) + else: + try: + log("LXMF - No Primary Identity file found, creating new...") + self.identity = RNS.Identity() + self.identity.to_file(self.identity_path) + log("LXMF - Created new Primary Identity %s" % (str(self.identity))) + except Exception as e: + log("LXMF - Could not create and save a new Primary Identity", LOG_ERROR) + log("LXMF - The contained exception was: %s" % (str(e)), LOG_ERROR) + + self.message_router = LXMF.LXMRouter(identity=self.identity, storagepath=self.storage_path) + + if self.destination_name == "lxmf" and self.destination_type == "delivery": + self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) + self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + else: + self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, self.destination_name, self.destination_type) + + if self.display_name == "": + self.display_name = RNS.prettyhexrep(self.destination_hash()) + + self.destination.set_default_app_data(self.display_name.encode("utf-8")) + + self.destination.set_proof_strategy(RNS.Destination.PROVE_ALL) + + RNS.Identity.remember(packet_hash=None, destination_hash=self.destination.hash, public_key=self.identity.get_public_key(), app_data=None) + + log("LXMF - Identity: " + str(self.identity), LOG_INFO) + log("LXMF - Destination: " + str(self.destination), LOG_INFO) + log("LXMF - Hash: " + RNS.prettyhexrep(self.destination_hash()), LOG_INFO) + + self.destination.set_link_established_callback(self.client_connected) + + self.autoselect_propagation_node() + + if self.announce_startup or self.announce_periodic: + self.announce(True) + + if self.sync_startup or self.sync_periodic: + self.sync(True) + + + def register_announce_callback(self, handler_function): + self.announce_callback = handler_function(self.aspect_filter) + RNS.Transport.register_announce_handler(self.announce_callback) + + + def register_message_received_callback(self, handler_function): + self.message_received_callback = handler_function + + + def register_message_notification_callback(self, handler_function): + self.message_notification_callback = handler_function + + + def register_message_notification_success_callback(self, handler_function): + self.message_notification_success_callback = handler_function + + + def register_message_notification_failed_callback(self, handler_function): + self.message_notification_failed_callback = handler_function + + + def destination_hash(self): + return self.destination.hash + + + def destination_hash_str(self): + return RNS.hexrep(self.destination.hash, False) + + + def destination_check(self, destination): + if type(destination) is not bytes: + if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2: + destination = destination[1:-1] + + if len(destination) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2): + log("LXMF - Destination length is invalid", LOG_ERROR) + return False + + try: + destination = bytes.fromhex(destination) + except Exception as e: + log("LXMF - Destination is invalid", LOG_ERROR) + return False + + return True + + + def destination_correct(self, destination): + if type(destination) is not bytes: + if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2: + destination = destination[1:-1] + + if len(destination) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2): + return "" + + try: + destination_bytes = bytes.fromhex(destination) + return destination + except Exception as e: + return "" + + return "" + + + def send(self, destination, content="", title="", fields=None, timestamp=None, app_data=""): + if type(destination) is not bytes: + if len(destination) == ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2)+2: + destination = destination[1:-1] + + if len(destination) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2): + log("LXMF - Destination length is invalid", LOG_ERROR) + return + + try: + destination = bytes.fromhex(destination) + except Exception as e: + log("LXMF - Destination is invalid", LOG_ERROR) + return + + destination_identity = RNS.Identity.recall(destination) + destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, self.destination_name, self.destination_type) + self.send_message(destination, self.destination, content, title, fields, timestamp, app_data) + + + def send_message(self, destination, source, content="", title="", fields=None, timestamp=None, app_data=""): + if self.desired_method_direct: + desired_method = LXMF.LXMessage.DIRECT + else: + desired_method = LXMF.LXMessage.PROPAGATED + + message = LXMF.LXMessage(destination, source, content, title=title, desired_method=desired_method) + + if fields is not None: + message.fields = fields + + if timestamp is not None: + message.timestamp = timestamp + + message.app_data = app_data + + self.message_method(message) + self.log_message(message, "LXMF - Message send") + + message.register_delivery_callback(self.message_notification) + message.register_failed_callback(self.message_notification) + + if self.message_router.get_outbound_propagation_node() != None: + message.try_propagation_on_fail = self.try_propagation_on_fail + + try: + self.message_router.handle_outbound(message) + time.sleep(self.send_delay) + except Exception as e: + log("LXMF - Could not send message " + str(message), LOG_ERROR) + log("LXMF - The contained exception was: " + str(e), LOG_ERROR) + return + + + def message_notification(self, message): + self.message_method(message) + + if self.message_notification_callback is not None: + self.message_notification_callback(message) + + if message.state == LXMF.LXMessage.FAILED and hasattr(message, "try_propagation_on_fail") and message.try_propagation_on_fail: + self.log_message(message, "LXMF - Delivery receipt (failed) Retrying as propagated message") + message.try_propagation_on_fail = None + message.delivery_attempts = 0 + del message.next_delivery_attempt + message.packed = None + message.desired_method = LXMF.LXMessage.PROPAGATED + self.message_router.handle_outbound(message) + elif message.state == LXMF.LXMessage.FAILED: + self.log_message(message, "LXMF - Delivery receipt (failed)") + if self.message_notification_failed_callback is not None: + self.message_notification_failed_callback(message) + else: + self.log_message(message, "LXMF - Delivery receipt (success)") + if self.message_notification_success_callback is not None: + self.message_notification_success_callback(message) + + + def message_method(self, message): + if message.desired_method == LXMF.LXMessage.DIRECT: + message.desired_method_str = "direct" + elif message.desired_method == LXMF.LXMessage.PROPAGATED: + message.desired_method_str = "propagated" + + + def announce(self, initial=False): + announce_timer = None + + if self.announce_periodic and self.announce_periodic_interval > 0: + announce_timer = threading.Timer(self.announce_periodic_interval*60, self.announce) + announce_timer.daemon = True + announce_timer.start() + + if initial: + if self.announce_startup: + if self.announce_startup_delay > 0: + if announce_timer is not None: + announce_timer.cancel() + announce_timer = threading.Timer(self.announce_startup_delay, self.announce) + announce_timer.daemon = True + announce_timer.start() + else: + self.announce_now() + return + + self.announce_now() + + + def announce_now(self, app_data=None): + if app_data: + if isinstance(app_data, str): + self.destination.announce(app_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG) + else: + self.destination.announce(app_data) + log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) + elif self.announce_data: + if isinstance(self.announce_data, str): + self.destination.announce(self.announce_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG) + else: + self.destination.announce(self.announce_data) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) + else: + self.destination.announce() + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ": " + self.display_name, LOG_DEBUG) + + + def sync(self, initial=False): + sync_timer = None + + if self.sync_periodic and self.sync_periodic_interval > 0: + sync_timer = threading.Timer(self.sync_periodic_interval*60, self.sync) + sync_timer.daemon = True + sync_timer.start() + + if initial: + if self.sync_startup: + if self.sync_startup_delay > 0: + if sync_timer is not None: + sync_timer.cancel() + sync_timer = threading.Timer(self.sync_startup_delay, self.sync) + sync_timer.daemon = True + sync_timer.start() + else: + self.sync_now(self.sync_limit) + return + + self.sync_now(self.sync_limit) + + + def sync_now(self, limit=None): + if self.message_router.get_outbound_propagation_node() is not None: + if self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_IDLE or self.message_router.propagation_transfer_state == LXMF.LXMRouter.PR_COMPLETE: + log("LXMF - Message sync requested from propagation node " + RNS.prettyhexrep(self.message_router.get_outbound_propagation_node()) + " for " + str(self.identity)) + self.message_router.request_messages_from_propagation_node(self.identity, max_messages = limit) + return True + else: + return False + else: + return False + + + def autoselect_propagation_node(self): + if self.propagation_node is not None: + if len(self.propagation_node) != ((RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2): + log("LXMF - Propagation node length is invalid", LOG_ERROR) + else: + try: + propagation_hash = bytes.fromhex(self.propagation_node) + except Exception as e: + log("LXMF - Propagation node is invalid", LOG_ERROR) + return + + node_identity = RNS.Identity.recall(propagation_hash) + if node_identity != None: + log("LXMF - Propagation node: " + RNS.prettyhexrep(propagation_hash), LOG_INFO) + propagation_hash = RNS.Destination.hash_from_name_and_identity("lxmf.propagation", node_identity) + self.message_router.set_outbound_propagation_node(propagation_hash) + else: + log("LXMF - Propagation node identity not known", LOG_ERROR) + + + def client_connected(self, link): + log("LXMF - Client connected " + str(link), LOG_EXTREME) + link.set_resource_strategy(RNS.Link.ACCEPT_ALL) + link.set_resource_concluded_callback(self.resource_concluded) + link.set_packet_callback(self.packet_received) + + + def packet_received(self, lxmf_bytes, packet): + log("LXMF - Single packet delivered " + str(packet), LOG_EXTREME) + self.process_lxmf_message_bytes(lxmf_bytes) + + + def resource_concluded(self, resource): + log("LXMF - Resource data transfer (multi packet) delivered " + str(resource.file), LOG_EXTREME) + if resource.status == RNS.Resource.COMPLETE: + lxmf_bytes = resource.data.read() + self.process_lxmf_message_bytes(lxmf_bytes) + else: + log("LXMF - Received resource message is not complete", LOG_EXTREME) + + + def process_lxmf_message_bytes(self, lxmf_bytes): + try: + message = LXMF.LXMessage.unpack_from_bytes(lxmf_bytes) + except Exception as e: + log("LXMF - Could not assemble LXMF message from received data", LOG_ERROR) + log("LXMF - The contained exception was: " + str(e), LOG_ERROR) + return + + message.desired_method = LXMF.LXMessage.DIRECT + + self.message_method(message) + self.log_message(message, "LXMF - Message received") + + if self.message_received_callback is not None: + log("LXMF - Call to registered message received callback", LOG_DEBUG) + self.message_received_callback(message) + else: + log("LXMF - No message received callback registered", LOG_DEBUG) + + + def process_lxmf_message_propagated(self, message): + message.desired_method = LXMF.LXMessage.PROPAGATED + + self.message_method(message) + self.log_message(message, "LXMF - Message received") + + if self.message_received_callback is not None: + log("LXMF - Call to registered message received callback", LOG_DEBUG) + self.message_received_callback(message) + else: + log("LXMF - No message received callback registered", LOG_DEBUG) + + + def log_message(self, message, message_tag="LXMF - Message log"): + if message.signature_validated: + signature_string = "Validated" + else: + if message.unverified_reason == LXMF.LXMessage.SIGNATURE_INVALID: + signature_string = "Invalid signature" + elif message.unverified_reason == LXMF.LXMessage.SOURCE_UNKNOWN: + signature_string = "Cannot verify, source is unknown" + else: + signature_string = "Signature is invalid, reason undetermined" + title = message.title.decode('utf-8') + content = message.content.decode('utf-8') + fields = message.fields + log(message_tag + ":", LOG_DEBUG) + log("- Date/Time: " + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(message.timestamp)), LOG_DEBUG) + log("- Title: " + title, LOG_DEBUG) + log("- Content: " + content, LOG_DEBUG) + log("- Fields: " + str(fields), LOG_DEBUG) + log("- Size: " + str(len(title) + len(content) + len(title) + len(pickle.dumps(fields))) + " bytes", LOG_DEBUG) + log("- Source: " + RNS.prettyhexrep(message.source_hash), LOG_DEBUG) + log("- Destination: " + RNS.prettyhexrep(message.destination_hash), LOG_DEBUG) + log("- Signature: " + signature_string, LOG_DEBUG) + log("- Attempts: " + str(message.delivery_attempts), LOG_DEBUG) + if hasattr(message, "desired_method_str"): + log("- Method: " + message.desired_method_str + " (" + str(message.desired_method) + ")", LOG_DEBUG) + else: + log("- Method: " + str(message.desired_method), LOG_DEBUG) + if hasattr(message, "app_data"): + log("- App Data: " + message.app_data, LOG_DEBUG) + + +############################################################################################################## +# LXMF Functions + + +#### LXMF - Announce #### +class lxmf_announce_callback: + def __init__(self, aspect_filter=None): + self.aspect_filter = aspect_filter + + + @staticmethod + def received_announce(destination_hash, announced_identity, app_data): + if app_data != None: + log("LXMF - Received an announce from " + RNS.prettyhexrep(destination_hash) + ": " + app_data.decode("utf-8"), LOG_INFO) + + + + +#### LXMF - Message #### +def lxmf_message_received_callback(message): + global CACHE + + if CONFIG["lxmf"].getboolean("signature_validated") and not message.signature_validated: + log("LXMF - Source " + RNS.prettyhexrep(message.source_hash) + " have no valid signature", LOG_DEBUG) + return + + if not message.fields: + return + + if not "registration_request" in message.fields and not "telemetry" in message.fields: + return + + db = None + try: + db = psycopg2.connect(user=CONFIG["database"]["user"], password=CONFIG["database"]["password"], host=CONFIG["database"]["host"], port=CONFIG["database"]["port"], database=CONFIG["database"]["database"]) + dbc = db.cursor() + + if "registration_request" in message.fields: + dbc.execute("INSERT INTO "+CONFIG["database"]["table_registration"]+" (hash, data) VALUES(%s, %s)", ( + RNS.hexrep(message.source_hash, delimit=False), + umsgpack.packb(message.fields["registration_request"])) + ) + + if "telemetry" in message.fields: + dbc.execute("INSERT INTO "+CONFIG["database"]["table_telemetry"]+" (hash, data) VALUES(%s, %s)", ( + RNS.hexrep(message.source_hash, delimit=False), + umsgpack.packb(message.fields["telemetry"])) + ) + + db.commit() + except psycopg2.DatabaseError as e: + log("DB - Error: "+str(e), LOG_ERROR) + if db: + dbc.close() + db.close() + db = None + + +############################################################################################################## +# Config + + +#### Config - Get ##### +def config_get(config, section, key, default="", lng_key=""): + if not config or section == "" or key == "": return default + if not config.has_section(section): return default + if config.has_option(section, key+lng_key): + return config[section][key+lng_key] + elif config.has_option(section, key): + return config[section][key] + return default + + +def config_getint(config, section, key, default=0, lng_key=""): + if not config or section == "" or key == "": return default + if not config.has_section(section): return default + if config.has_option(section, key+lng_key): + return config.getint(section, key+lng_key) + elif config.has_option(section, key): + return config.getint(section, key) + return default + + +def config_getboolean(config, section, key, default=False, lng_key=""): + if not config or section == "" or key == "": return default + if not config.has_section(section): return default + if config.has_option(section, key+lng_key): + return config[section].getboolean(key+lng_key) + elif config.has_option(section, key): + return config[section].getboolean(key) + return default + + +def config_getsection(config, section, default="", lng_key=""): + if not config or section == "": return default + if not config.has_section(section): return default + if config.has_section(section+lng_key): + return key+lng_key + elif config.has_section(section): + return key + return default + + +def config_getoption(config, section, key, default=False, lng_key=""): + if not config or section == "" or key == "": return default + if not config.has_section(section): return default + if config.has_option(section, key+lng_key): + return key+lng_key + elif config.has_option(section, key): + return key + return default + + + + +#### Config - Read ##### +def config_read(file=None, file_override=None): + global CONFIG + + if file is None: + return False + else: + CONFIG = configparser.ConfigParser(allow_no_value=True, inline_comment_prefixes="#") + CONFIG.sections() + if os.path.isfile(file): + try: + if file_override is None: + CONFIG.read(file, encoding='utf-8') + elif os.path.isfile(file_override): + CONFIG.read([file, file_override], encoding='utf-8') + else: + CONFIG.read(file, encoding='utf-8') + except Exception as e: + return False + else: + if not config_default(file=file, file_override=file_override): + return False + return True + + + + +#### Config - Save ##### +def config_save(file=None): + global CONFIG + + if file is None: + return False + else: + if os.path.isfile(file): + try: + with open(file,"w") as file: + CONFIG.write(file) + except Exception as e: + return False + else: + return False + return True + + + + +#### Config - Default ##### +def config_default(file=None, file_override=None): + global CONFIG + + if file is None: + return False + elif DEFAULT_CONFIG != "": + if file_override and DEFAULT_CONFIG_OVERRIDE != "": + if not os.path.isdir(os.path.dirname(file_override)): + try: + os.makedirs(os.path.dirname(file_override)) + except Exception: + return False + if not os.path.exists(file_override): + try: + config_file = open(file_override, "w") + config_file.write(DEFAULT_CONFIG_OVERRIDE) + config_file.close() + except: + return False + + if not os.path.isdir(os.path.dirname(file)): + try: + os.makedirs(os.path.dirname(file)) + except Exception: + return False + try: + config_file = open(file, "w") + config_file.write(DEFAULT_CONFIG) + config_file.close() + if not config_read(file=file, file_override=file_override): + return False + except: + return False + else: + return False + + if not CONFIG.has_section("main"): CONFIG.add_section("main") + CONFIG["main"]["default_config"] = "True" + return True + + +############################################################################################################## +# Value convert + + +def val_to_bool(val): + if val == "on" or val == "On" or val == "true" or val == "True" or val == "yes" or val == "Yes" or val == "1" or val == "open" or val == "opened" or val == "up": + return True + elif val == "off" or val == "Off" or val == "false" or val == "False" or val == "no" or val == "No" or val == "0" or val == "close" or val == "closed" or val == "down": + return False + elif val != "": + return True + else: + return False + + +############################################################################################################## +# Log + + +LOG_FORCE = -1 +LOG_CRITICAL = 0 +LOG_ERROR = 1 +LOG_WARNING = 2 +LOG_NOTICE = 3 +LOG_INFO = 4 +LOG_VERBOSE = 5 +LOG_DEBUG = 6 +LOG_EXTREME = 7 + +LOG_LEVEL = LOG_NOTICE +LOG_LEVEL_SERVICE = LOG_NOTICE +LOG_TIMEFMT = "%Y-%m-%d %H:%M:%S" +LOG_MAXSIZE = 5*1024*1024 +LOG_PREFIX = "" +LOG_SUFFIX = "" +LOG_FILE = "" + + + + +def log(text, level=3, file=None): + if not LOG_LEVEL: + return + + if LOG_LEVEL >= level: + name = "Unknown" + if (level == LOG_FORCE): + name = "" + if (level == LOG_CRITICAL): + name = "Critical" + if (level == LOG_ERROR): + name = "Error" + if (level == LOG_WARNING): + name = "Warning" + if (level == LOG_NOTICE): + name = "Notice" + if (level == LOG_INFO): + name = "Info" + if (level == LOG_VERBOSE): + name = "Verbose" + if (level == LOG_DEBUG): + name = "Debug" + if (level == LOG_EXTREME): + name = "Extra" + + if not isinstance(text, str): + text = str(text) + + text = "[" + time.strftime(LOG_TIMEFMT, time.localtime(time.time())) +"] [" + name + "] " + LOG_PREFIX + text + LOG_SUFFIX + + if file == None and LOG_FILE != "": + file = LOG_FILE + + if file == None: + print(text) + else: + try: + file_handle = open(file, "a") + file_handle.write(text + "\n") + file_handle.close() + + if os.path.getsize(file) > LOG_MAXSIZE: + file_prev = file + ".1" + if os.path.isfile(file_prev): + os.unlink(file_prev) + os.rename(file, file_prev) + except: + return + + +############################################################################################################## +# System + + +#### Panic ##### +def panic(): + sys.exit(255) + + +#### Exit ##### +def exit(): + sys.exit(0) + + +############################################################################################################## +# Setup/Start + + +#### Setup ##### +def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False): + global PATH + global PATH_RNS + global LOG_LEVEL + global LOG_FILE + global RNS_CONNECTION + global LXMF_CONNECTION + + if path is not None: + if path.endswith("/"): + path = path[:-1] + PATH = path + + if path_rns is not None: + if path_rns.endswith("/"): + path_rns = path_rns[:-1] + PATH_RNS = path_rns + + if loglevel is not None: + LOG_LEVEL = loglevel + rns_loglevel = loglevel + else: + rns_loglevel = None + + if service: + LOG_LEVEL = LOG_LEVEL_SERVICE + if path_log is not None: + if path_log.endswith("/"): + path_log = path_log[:-1] + LOG_FILE = path_log + else: + LOG_FILE = PATH + LOG_FILE = LOG_FILE + "/" + NAME + ".log" + rns_loglevel = None + + if not config_read(PATH + "/config.cfg", PATH + "/config.cfg.owr"): + print("Config - Error reading config file " + PATH + "/config.cfg") + panic() + + if CONFIG["main"].getboolean("default_config"): + print("Exit!") + print("First start with the default config!") + print("You should probably edit the config file \"" + PATH + "/config.cfg\" to suit your needs and use-case!") + print("You should make all your changes at the user configuration file \"" + PATH + "/config.cfg.owr\" to override the default configuration file!") + print("Then restart this program again!") + exit() + + if not CONFIG["main"].getboolean("enabled"): + print("Disabled in config file. Exit!") + exit() + + RNS_CONNECTION = RNS.Reticulum(configdir=PATH_RNS, loglevel=rns_loglevel) + + log("...............................................................................", LOG_INFO) + log(" Name: " + CONFIG["main"]["name"], LOG_INFO) + log("Program File: " + __file__, LOG_INFO) + log(" Config File: " + PATH + "/config", LOG_INFO) + log(" Version: " + VERSION, LOG_INFO) + log(" Copyright: " + COPYRIGHT, LOG_INFO) + log("...............................................................................", LOG_INFO) + + log("LXMF - Connecting ...", LOG_DEBUG) + + if CONFIG.has_option("lxmf", "propagation_node"): + config_propagation_node = CONFIG["lxmf"]["propagation_node"] + else: + config_propagation_node = None + + if path is None: + path = PATH + + announce_data = {} + section = "data" + if CONFIG.has_section(section): + for (key, val) in CONFIG.items(section): + announce_data[key] = val + + LXMF_CONNECTION = lxmf_connection( + storage_path=path, + destination_name=CONFIG["lxmf"]["destination_name"], + destination_type=CONFIG["lxmf"]["destination_type"], + display_name=CONFIG["lxmf"]["display_name"], + announce_data = umsgpack.packb(announce_data), + send_delay=CONFIG["lxmf"]["send_delay"], + desired_method=CONFIG["lxmf"]["desired_method"], + propagation_node=config_propagation_node, + try_propagation_on_fail=CONFIG["lxmf"].getboolean("try_propagation_on_fail"), + announce_startup=CONFIG["lxmf"].getboolean("announce_startup"), + announce_startup_delay=CONFIG["lxmf"]["announce_startup_delay"], + announce_periodic=CONFIG["lxmf"].getboolean("announce_periodic"), + announce_periodic_interval=CONFIG["lxmf"]["announce_periodic_interval"], + sync_startup=CONFIG["lxmf"].getboolean("sync_startup"), + sync_startup_delay=CONFIG["lxmf"]["sync_startup_delay"], + sync_limit=CONFIG["lxmf"]["sync_limit"], + sync_periodic=CONFIG["lxmf"].getboolean("sync_periodic"), + sync_periodic_interval=CONFIG["lxmf"]["sync_periodic_interval"]) + + LXMF_CONNECTION.register_announce_callback(lxmf_announce_callback) + LXMF_CONNECTION.register_message_received_callback(lxmf_message_received_callback) + + log("LXMF - Connected", LOG_DEBUG) + + log("...............................................................................", LOG_FORCE) + log("LXMF - Address: " + RNS.prettyhexrep(LXMF_CONNECTION.destination_hash()), LOG_FORCE) + log("...............................................................................", LOG_FORCE) + + + while True: + time.sleep(1) + + + + +#### Start #### +def main(): + try: + description = NAME + " - " + DESCRIPTION + parser = argparse.ArgumentParser(description=description) + + parser.add_argument("-p", "--path", action="store", type=str, default=None, help="Path to alternative config directory") + parser.add_argument("-pr", "--path_rns", action="store", type=str, default=None, help="Path to alternative Reticulum config directory") + parser.add_argument("-pl", "--path_log", action="store", type=str, default=None, help="Path to alternative log directory") + parser.add_argument("-l", "--loglevel", action="store", type=int, default=LOG_LEVEL) + parser.add_argument("-s", "--service", action="store_true", default=False, help="Running as a service and should log to file") + parser.add_argument("--exampleconfig", action="store_true", default=False, help="Print verbose configuration example to stdout and exit") + parser.add_argument("--exampleconfigoverride", action="store_true", default=False, help="Print verbose configuration example to stdout and exit") + + params = parser.parse_args() + + if params.exampleconfig: + print("Config File: " + PATH + "/config.cfg") + print("Content:") + print(DEFAULT_CONFIG) + exit() + + if params.exampleconfigoverride: + print("Config Override File: " + PATH + "/config.cfg.owr") + print("Content:") + print(DEFAULT_CONFIG_OVERRIDE) + exit() + + setup(path=params.path, path_rns=params.path_rns, path_log=params.path_log, loglevel=params.loglevel, service=params.service) + + except KeyboardInterrupt: + print("Terminated by CTRL-C") + exit() + + +############################################################################################################## +# Files + + +#### Default configuration override file #### +DEFAULT_CONFIG_OVERRIDE = '''# This is the user configuration file to override the default configuration file. +# All settings made here have precedence. +# This file can be used to clearly summarize all settings that deviate from the default. +# This also has the advantage that all changed settings can be kept when updating the program. + +[lxmf] +announce_periodic = Yes +announce_periodic_interval = 15 #Minutes + +[data] +v_s = 0.0.0 #Version software +v_c = 2022-01-01 00:00 #Version config +v_d = 2022-01-01 00:00 #Version data +v_a = 2022-01-01 00:00 #Version auth +u_s = #URL Software +''' + + +#### Default configuration file #### +DEFAULT_CONFIG = '''# This is the default config file. +# You should probably edit it to suit your needs and use-case. + + + + +#### Main program settings #### +[main] + +enabled = True + +# Name of the program. Only for display in the log or program startup. +name = LXMF Provisioning Server + + + + +#### LXMF connection settings #### +[lxmf] + +# Destination name & type need to fits the LXMF protocoll +# to be compatibel with other LXMF programs. +destination_name = lxmf +destination_type = provisioning + +# The name will be visible to other peers +# on the network, and included in announces. +display_name = LXMF Provisioning Server + +# Default send method. +desired_method = direct #direct/propagated + +# Propagation node address/hash. +#propagation_node = + +# Try to deliver a message via the LXMF propagation network, +# if a direct delivery to the recipient is not possible. +try_propagation_on_fail = No + +# The peer is announced at startup +# to let other peers reach it immediately. +announce_startup = Yes +announce_startup_delay = 0 #Seconds + +# The peer is announced periodically +# to let other peers reach it. +announce_periodic = Yes +announce_periodic_interval = 360 #Minutes + +# Some waiting time after message send +# for LXMF/Reticulum processing. +send_delay = 0 #Seconds + +# Sync LXMF messages at startup. +sync_startup = No +sync_startup_delay = 0 #Seconds + +# Sync LXMF messages periodically. +sync_periodic = No + +# The sync interval in minutes. +sync_periodic_interval = 360 #Minutes + +# Automatic LXMF syncs will only +# download x messages at a time. You can change +# this number, or set the option to 0 to disable +# the limit, and download everything every time. +sync_limit = 8 + +# Allow only messages with valid signature. +signature_validated = Yes + + + + +#### Database connection settings #### +[database] + +host = 127.0.0.1 +port = 5432 +user = postgres +password = password +database = database +table_registration = tbl_account +table_telemetry = tbl_telemetry + + + + +#### Data settings #### +[data] + +v_s = 0.0.0 #Version software +v_c = 2022-01-01 00:00 #Version config +v_d = 2022-01-01 00:00 #Version data +v_a = 2022-01-01 00:00 #Version auth +u_s = #URL Software +''' + + +############################################################################################################## +# Init + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/lxmf_terminal/lxmf_terminal.py b/lxmf_terminal/lxmf_terminal.py index 66813f6..a557981 100755 --- a/lxmf_terminal/lxmf_terminal.py +++ b/lxmf_terminal/lxmf_terminal.py @@ -195,7 +195,7 @@ class lxmf_connection: message_notification_failed_callback = None - def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): + def __init__(self, storage_path=None, identity_file="identity", identity=None, destination_name="lxmf", destination_type="delivery", display_name="", announce_data=None, send_delay=0, desired_method="direct", propagation_node=None, try_propagation_on_fail=False, announce_startup=False, announce_startup_delay=0, announce_periodic=False, announce_periodic_interval=360, sync_startup=False, sync_startup_delay=0, sync_limit=8, sync_periodic=False, sync_periodic_interval=360): self.storage_path = storage_path self.identity_file = identity_file @@ -207,6 +207,7 @@ class lxmf_connection: self.aspect_filter = self.destination_name + "." + self.destination_type self.display_name = display_name + self.announce_data = announce_data self.send_delay = int(send_delay) @@ -262,9 +263,11 @@ class lxmf_connection: self.message_router = LXMF.LXMRouter(identity=self.identity, storagepath=self.storage_path) - self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) - - self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + if self.destination_name == "lxmf" and self.destination_type == "delivery": + self.destination = self.message_router.register_delivery_identity(self.identity, display_name=self.display_name) + self.message_router.register_delivery_callback(self.process_lxmf_message_propagated) + else: + self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, self.destination_name, self.destination_type) if self.display_name == "": self.display_name = RNS.prettyhexrep(self.destination_hash()) @@ -464,8 +467,19 @@ class lxmf_connection: def announce_now(self, app_data=None): if app_data: - self.destination.announce(app_data.encode("utf-8")) - log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ":" + app_data, LOG_DEBUG) + if isinstance(app_data, str): + self.destination.announce(app_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + announce_data, LOG_DEBUG) + else: + self.destination.announce(app_data) + log("LMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) + elif self.announce_data: + if isinstance(self.announce_data, str): + self.destination.announce(self.announce_data.encode("utf-8")) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) +":" + self.announce_data, LOG_DEBUG) + else: + self.destination.announce(self.announce_data) + log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()), LOG_DEBUG) else: self.destination.announce() log("LXMF - Announced: " + RNS.prettyhexrep(self.destination_hash()) + ": " + self.display_name, LOG_DEBUG) From 4d870ad1139eb17edf3637a69a72648ae320561d Mon Sep 17 00:00:00 2001 From: SebastianObi Date: Thu, 8 Dec 2022 13:06:12 +0100 Subject: [PATCH 2/2] Working on lxmf_provisioning --- lxmf_provisioning/README.md | 259 +++++++++++++++++++++++++ lxmf_provisioning/lxmf_provisioning.py | 28 ++- 2 files changed, 281 insertions(+), 6 deletions(-) diff --git a/lxmf_provisioning/README.md b/lxmf_provisioning/README.md index 10b69ec..8d1382e 100644 --- a/lxmf_provisioning/README.md +++ b/lxmf_provisioning/README.md @@ -1,2 +1,261 @@ # lxmf_provisioning +This program offers the possibility of provisioning clients. This includes, for example: The announcement of software updates. Registration of new users. Saving telemetry data that the clients send to the server. The data is stored in a PostgreSQL database. The source code can of course be customized to store the data in a different way. + For more information, see the configuration options (at the end of the program files). Everything else is briefly documented there. After the first start this configuration will be created as default config in the corresponding file. + + +### Features +- Announcement of the server and software versions +- User registration +- Collection of telemetry data +- Storage of data in PostgreSQL, ... + + +## Examples of use + +### General info how the messages/data are transported +All announcements are transmitted unencrypted with their own type/name which is not shown in the Nomadnet/Sideband announcement list. +All messages between client<->server are transported as single 1:1 messages in the LXMF/Reticulum network. +Accordingly, encryption takes place between these end points. + + +## Current Status +It should currently be considered beta software and still work in progress. + +All core features are implemented and functioning, but additions will probably occur as real-world use is explored. + +There may be errors or the compatibility after an update is no longer guaranteed. + +The full documentation is not yet available. Due to lack of time I can also not say when this will be further processed. + + +## Screenshots / Usage examples + + +## Installation manual + +### Install: +- Install all required prerequisites. (Default Reticulum installation. Only necessary if reticulum is not yet installed.) + ```bash + apt update + apt upgrade + + apt install python3-pip + + pip install pip --upgrade + reboot + + pip3 install rns + pip3 install pyserial netifaces + + pip3 install lxmf + ``` +- Change the Reticulum configuration to suit your needs and use-case. + ```bash + nano /.reticulum/config + ``` +- Download the [file](lxmf_provisioning.py) from this repository. + ```bash + wget https://raw.githubusercontent.com/SebastianObi/LXMF-Tools/main/lxmf_provisioning/lxmf_provisioning.py + ``` +- Make it executable with the following command + ```bash + chmod +x lxmf_provisioning.py + ``` + +### Start: +- Start it + ```bash + ./lxmf_provisioning.py + ``` +- After the first start edit the configuration file to suit your needs and use-case. The file location is displayed. +- Example minimal configuration (override of the default config `config.cfg`). These are the most relevant settings that need to be adjusted. All other settings are in `config.cfg` + ```bash + nano /root/.lxmf_provisioning/config.cfg.owr + ``` + ```bash + ``` +- Start it again. Finished! + ```bash + ./lxmf_provisioning.py + ``` + + +### Run as a system service/deamon: +- Create a service file. + ```bash + nano /etc/systemd/system/lxmf_provisioning.service + ``` +- Copy and edit the following content to your own needs. + ```bash + [Unit] + Description=LXMF Provisioning Daemon + After=multi-user.target + [Service] + # ExecStartPre=/bin/sleep 10 + Type=simple + Restart=always + RestartSec=3 + User=root + ExecStart=/root/lxmf_provisioning.py + [Install] + WantedBy=multi-user.target + ``` +- Enable the service. + ```bash + systemctl enable lxmf_provisioning + ``` +- Start the service. + ```bash + systemctl start lxmf_provisioning + ``` + + +### Start/Stop service: + ```bash + systemctl start lxmf_provisioning + systemctl stop lxmf_provisioning + ``` + + +### Enable/Disable service: + ```bash + systemctl enable lxmf_provisioning + systemctl disable lxmf_provisioning + ``` + + +### Run several instances (To copy the same application): +- Run the program with a different configuration path. + ```bash + ./lxmf_provisioning.py -p /root/.lxmf_provisioning_2nd + ./lxmf_provisioning.py -p /root/.lxmf_provisioning_3nd + ``` +- After the first start edit the configuration file to suit your needs and use-case. The file location is displayed. + + +### First usage: +- With a manual start via the console, the own LXMF address is displayed: + ``` + [] ............................................................................... + [] LXMF - Address: <801f48d54bc71cb3e0886944832aaf8d> + [] ...............................................................................` + ``` +- This address is also annouced at startup in the default setting. +- This provisioning server address must be added to the clients. +- Now the software can be used. + + +### Startup parameters: +```bash +usage: lxmf_provisioning.py [-h] [-p PATH] [-pr PATH_RNS] [-pl PATH_LOG] [-l LOGLEVEL] [-s] [--exampleconfig] [--exampleconfigoverride] + +LXMF Provisioning Server - + +optional arguments: + -h, --help show this help message and exit + -p PATH, --path PATH Path to alternative config directory + -pr PATH_RNS, --path_rns PATH_RNS + Path to alternative Reticulum config directory + -pl PATH_LOG, --path_log PATH_LOG + Path to alternative log directory + -l LOGLEVEL, --loglevel LOGLEVEL + -s, --service Running as a service and should log to file + --exampleconfig Print verbose configuration example to stdout and exit + --exampleconfigoverride + Print verbose configuration example to stdout and exit +``` + + +### Config/data files: +- config.cfg + + This is the default config file. + +- config.cfg.owr + + This is the user configuration file to override the default configuration file. + All settings made here have precedence. + This file can be used to clearly summarize all settings that deviate from the default. + This also has the advantage that all changed settings can be kept when updating the program. + + +## Configuration manual (Examples) +The configurations shown here are only a part of the total configuration. +It only serves to show the configuration that is necessary and adapted for the respective function. +All configurations must be made in the file `config.cfg.owr`. +All possible settings can be seen in the default configuration file `config.cfg`. + + +### Standard function (Announce versions, user registration, telemetry): +- `config.cfg.owr` + ``` + [lxmf] + announce_periodic = Yes + announce_periodic_interval = 15 #Minutes + + [database] + host = 127.0.0.1 + port = 5432 + user = postgres + password = password + database = test + table_registration = tbl_account + table_telemetry = tbl_telemetry + + [features] + announce_versions = True + registration = True + telemetry = True + + [data] + v_s = 0.1.4 #Version software + v_c = 2022-11-29 20:00 #Version config + v_d = 2022-11-29 20:00 #Version data + v_a = 2022-11-29 20:00 #Version auth + u_s = https:// #URL Software + ``` + + +### Custom function (Announce versions): +- `config.cfg.owr` + ``` + [lxmf] + announce_periodic = Yes + announce_periodic_interval = 15 #Minutes + + [database] + host = 127.0.0.1 + port = 5432 + user = postgres + password = password + database = test + table_registration = tbl_account + table_telemetry = tbl_telemetry + + [features] + announce_versions = True + registration = False + telemetry = False + + [data] + v_s = 0.1.4 #Version software + v_c = 2022-11-29 20:00 #Version config + v_d = 2022-11-29 20:00 #Version data + v_a = 2022-11-29 20:00 #Version auth + u_s = https:// #URL Software + ``` + + +## Admin manual +This guide applies to all admins. Here are briefly explained the administative possibilities. + + +## User manual +This guide applies to users or admins. Here are briefly explained the normal possibilities of the software. + + +## FAQ + +### How do I start with the software? +You should read the `Installation manual` section. There everything is explained briefly. Just work through everything from top to bottom :) \ No newline at end of file diff --git a/lxmf_provisioning/lxmf_provisioning.py b/lxmf_provisioning/lxmf_provisioning.py index b5be2ab..563917d 100755 --- a/lxmf_provisioning/lxmf_provisioning.py +++ b/lxmf_provisioning/lxmf_provisioning.py @@ -566,13 +566,13 @@ def lxmf_message_received_callback(message): db = psycopg2.connect(user=CONFIG["database"]["user"], password=CONFIG["database"]["password"], host=CONFIG["database"]["host"], port=CONFIG["database"]["port"], database=CONFIG["database"]["database"]) dbc = db.cursor() - if "registration_request" in message.fields: + if CONFIG["features"].getboolean("registration") and "registration_request" in message.fields: dbc.execute("INSERT INTO "+CONFIG["database"]["table_registration"]+" (hash, data) VALUES(%s, %s)", ( RNS.hexrep(message.source_hash, delimit=False), umsgpack.packb(message.fields["registration_request"])) ) - if "telemetry" in message.fields: + if CONFIG["features"].getboolean("telemetry") and "telemetry" in message.fields: dbc.execute("INSERT INTO "+CONFIG["database"]["table_telemetry"]+" (hash, data) VALUES(%s, %s)", ( RNS.hexrep(message.source_hash, delimit=False), umsgpack.packb(message.fields["telemetry"])) @@ -914,10 +914,11 @@ def setup(path=None, path_rns=None, path_log=None, loglevel=None, service=False) path = PATH announce_data = {} - section = "data" - if CONFIG.has_section(section): - for (key, val) in CONFIG.items(section): - announce_data[key] = val + if CONFIG["features"].getboolean("announce_versions"): + section = "data" + if CONFIG.has_section(section): + for (key, val) in CONFIG.items(section): + announce_data[key] = val LXMF_CONNECTION = lxmf_connection( storage_path=path, @@ -1004,6 +1005,11 @@ DEFAULT_CONFIG_OVERRIDE = '''# This is the user configuration file to override t announce_periodic = Yes announce_periodic_interval = 15 #Minutes +[features] +announce_versions = True +registration = True +telemetry = False + [data] v_s = 0.0.0 #Version software v_c = 2022-01-01 00:00 #Version config @@ -1103,6 +1109,16 @@ table_telemetry = tbl_telemetry +#### Features enabled/disabled #### +[features] + +announce_versions = True +registration = True +telemetry = False + + + + #### Data settings #### [data]