summaryrefslogtreecommitdiff
path: root/src/core/service.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/service.c')
-rw-r--r--src/core/service.c1270
1 files changed, 1270 insertions, 0 deletions
diff --git a/src/core/service.c b/src/core/service.c
new file mode 100644
index 0000000..a8bd402
--- /dev/null
+++ b/src/core/service.c
@@ -0,0 +1,1270 @@
+/*
+ * Centralized daemon: INI apply, plugin lifecycle, HBX circuit hub, sessions,
+ * link registry prune. Wire protocols stay in plugins/ (telnet, packet_radio).
+ */
+#include "hybbx/service.h"
+#include "hybbx/session.h"
+#include "storage_private.h"
+#include "hybbx/registry.h"
+#include "hybbx/config.h"
+#include "hybbx/crypto_config.h"
+#include "hybbx/traffic.h"
+#include "hybbx/circuit_tcp.h"
+#include "hybbx/circuit_bridge.h"
+#include "hybbx/link.h"
+#include "hybbx/auth.h"
+#include "hybbx/storage.h"
+#include "hybbx/texts.h"
+#include "hybbx/chat.h"
+#include "hybbx/mail.h"
+#include "hybbx/broadcast.h"
+
+#ifdef HYBBX_HAVE_PLUGIN_MAINS_PROXY
+void hybbx_mains_proxy_plugin_tick(void);
+#endif
+#include "hybbx/networks.h"
+#include "hybbx/instance.h"
+#include "hybbx/log.h"
+#include "hybbx/monitor.h"
+#include "hybbx/security.h"
+#include "hybbx/security_ban.h"
+#include "hybbx/util.h"
+#include "hybbx/limits.h"
+
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#if defined(_WIN32)
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+#define HYBBX_MAX_ACTIVE_TRANSPORTS 8
+
+static char *hybbx_strdup(const char *s)
+{
+ size_t len;
+ char *copy;
+
+ if (s == NULL) {
+ return NULL;
+ }
+
+ len = strlen(s) + 1;
+ copy = malloc(len);
+ if (copy != NULL) {
+ memcpy(copy, s, len);
+ }
+ return copy;
+}
+
+typedef struct active_transport {
+ const hybbx_transport_plugin_t *plugin;
+ int running;
+} active_transport_t;
+
+typedef struct hybbx_attached_session {
+ struct hybbx_session *session;
+ struct hybbx_attached_session *next;
+} hybbx_attached_session_t;
+
+#define HYBBX_DEFAULT_DATA_PATH "data"
+
+struct hybbx_service_internal {
+ char *name;
+ char config_path[HYBBX_PATH_MAX];
+ char prompt[HYBBX_PROMPT_MAX];
+ unsigned max_online;
+ unsigned guest_timeout_minutes;
+ int login_announce;
+ unsigned active_nodes;
+ pthread_mutex_t node_lock;
+ pthread_mutex_t session_lock;
+ hybbx_attached_session_t *sessions;
+ hybbx_storage_t *storage;
+ hybbx_auth_config_t auth;
+ hybbx_texts_config_t texts;
+ hybbx_chat_config_t chat;
+ hybbx_mail_config_t mail;
+ hybbx_broadcast_config_t broadcast;
+ hybbx_networks_config_t networks;
+ active_transport_t transports[HYBBX_MAX_ACTIVE_TRANSPORTS];
+ size_t transport_count;
+ hybbx_circuit_hub_t *circuit_hub;
+ int running;
+ hybbx_shutdown_mode_t shutdown_mode;
+ char launch_binary[HYBBX_PATH_MAX];
+ pthread_mutex_t guest_lock;
+ unsigned char guest_in_use[HYBBX_GUEST_NUMBER_MAX + 1];
+};
+
+hybbx_service_t *hybbx_service_create(const char *name)
+{
+ struct hybbx_service_internal *svc;
+
+ svc = calloc(1, sizeof(*svc));
+ if (svc == NULL) {
+ return NULL;
+ }
+
+ hybbx_auth_config_defaults(&svc->auth);
+ hybbx_texts_config_defaults(&svc->texts);
+ hybbx_chat_config_defaults(&svc->chat);
+ hybbx_mail_config_defaults(&svc->mail);
+ hybbx_networks_config_defaults(&svc->networks);
+ svc->max_online = HYBBX_DEFAULT_MAX_ONLINE;
+ svc->guest_timeout_minutes = HYBBX_DEFAULT_GUEST_TIMEOUT_MINUTES;
+ svc->login_announce = 0;
+ svc->active_nodes = 0;
+ pthread_mutex_init(&svc->node_lock, NULL);
+ pthread_mutex_init(&svc->session_lock, NULL);
+ pthread_mutex_init(&svc->guest_lock, NULL);
+
+ svc->name = hybbx_strdup(name != NULL && name[0] != '\0' ?
+ name : HYBBX_DEFAULT_SERVICE_NAME);
+ if (svc->name == NULL) {
+ free(svc);
+ return NULL;
+ }
+
+ return (hybbx_service_t *)svc;
+}
+
+void hybbx_service_destroy(hybbx_service_t *service)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+ size_t i;
+
+ if (svc == NULL) {
+ return;
+ }
+
+ hybbx_service_stop(service);
+
+ if (svc->circuit_hub != NULL) {
+ hybbx_circuit_hub_destroy(svc->circuit_hub);
+ svc->circuit_hub = NULL;
+ }
+
+ for (i = 0; i < svc->transport_count; i++) {
+ if (svc->transports[i].running &&
+ svc->transports[i].plugin->stop != NULL) {
+ svc->transports[i].plugin->stop();
+ }
+ if (svc->transports[i].plugin->shutdown != NULL) {
+ svc->transports[i].plugin->shutdown();
+ }
+ }
+
+ if (svc->storage != NULL) {
+ hybbx_storage_close(svc->storage);
+ svc->storage = NULL;
+ }
+
+ hybbx_monitor_shutdown();
+ hybbx_log_shutdown();
+ hybbx_security_log_shutdown();
+ hybbx_security_ban_shutdown();
+
+ {
+ hybbx_attached_session_t *node = svc->sessions;
+
+ while (node != NULL) {
+ hybbx_attached_session_t *next = node->next;
+
+ free(node);
+ node = next;
+ }
+ svc->sessions = NULL;
+ }
+
+ pthread_mutex_destroy(&svc->session_lock);
+ pthread_mutex_destroy(&svc->guest_lock);
+ pthread_mutex_destroy(&svc->node_lock);
+ free(svc->name);
+ free(svc);
+}
+
+hybbx_storage_t *hybbx_service_get_storage(hybbx_service_t *service)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return NULL;
+ }
+
+ return svc->storage;
+}
+
+const hybbx_auth_config_t *hybbx_service_get_auth(hybbx_service_t *service)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return NULL;
+ }
+
+ return &svc->auth;
+}
+
+const hybbx_texts_config_t *hybbx_service_get_texts(hybbx_service_t *service)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return NULL;
+ }
+
+ return &svc->texts;
+}
+
+const hybbx_chat_config_t *hybbx_service_get_chat(const hybbx_service_t *service)
+{
+ const struct hybbx_service_internal *svc =
+ (const struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return NULL;
+ }
+
+ return &svc->chat;
+}
+
+const hybbx_mail_config_t *hybbx_service_get_mail(const hybbx_service_t *service)
+{
+ const struct hybbx_service_internal *svc =
+ (const struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return NULL;
+ }
+
+ return &svc->mail;
+}
+
+const hybbx_broadcast_config_t *hybbx_service_get_broadcast(
+ const hybbx_service_t *service)
+{
+ const struct hybbx_service_internal *svc =
+ (const struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return NULL;
+ }
+
+ return &svc->broadcast;
+}
+
+hybbx_circuit_hub_t *hybbx_service_circuit_hub(hybbx_service_t *service)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return NULL;
+ }
+
+ return svc->circuit_hub;
+}
+
+const char *hybbx_service_get_prompt(const hybbx_service_t *service)
+{
+ const struct hybbx_service_internal *svc =
+ (const struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return "";
+ }
+
+ return svc->prompt;
+}
+
+const char *hybbx_service_get_name(const hybbx_service_t *service)
+{
+ const struct hybbx_service_internal *svc =
+ (const struct hybbx_service_internal *)service;
+
+ if (svc == NULL || svc->name == NULL || svc->name[0] == '\0') {
+ return HYBBX_DEFAULT_SERVICE_NAME;
+ }
+
+ return svc->name;
+}
+
+unsigned hybbx_service_max_online(const hybbx_service_t *service)
+{
+ const struct hybbx_service_internal *svc =
+ (const struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return HYBBX_DEFAULT_MAX_ONLINE;
+ }
+
+ return svc->max_online;
+}
+
+int hybbx_service_login_announce(const hybbx_service_t *service)
+{
+ const struct hybbx_service_internal *svc =
+ (const struct hybbx_service_internal *)service;
+
+ return svc != NULL && svc->login_announce != 0;
+}
+
+unsigned hybbx_service_active_nodes(const hybbx_service_t *service)
+{
+ const struct hybbx_service_internal *svc =
+ (const struct hybbx_service_internal *)service;
+ unsigned count;
+
+ if (svc == NULL) {
+ return 0;
+ }
+
+ pthread_mutex_lock((pthread_mutex_t *)&svc->node_lock);
+ count = svc->active_nodes;
+ pthread_mutex_unlock((pthread_mutex_t *)&svc->node_lock);
+
+ return count;
+}
+
+unsigned hybbx_service_guest_timeout_seconds(const hybbx_service_t *service)
+{
+ const struct hybbx_service_internal *svc =
+ (const struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return HYBBX_DEFAULT_GUEST_TIMEOUT_MINUTES * 60u;
+ }
+
+ return svc->guest_timeout_minutes * 60u;
+}
+
+hybbx_result_t hybbx_service_guest_assign(hybbx_service_t *service,
+ const char *guest_prefix,
+ hybbx_user_record_t *out,
+ unsigned *slot_out)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+ unsigned slot;
+
+ if (svc == NULL || out == NULL || slot_out == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ pthread_mutex_lock(&svc->guest_lock);
+ for (slot = 1; slot <= HYBBX_GUEST_NUMBER_MAX; slot++) {
+ if (!svc->guest_in_use[slot]) {
+ svc->guest_in_use[slot] = 1;
+ pthread_mutex_unlock(&svc->guest_lock);
+
+ hybbx_guest_fill_record(guest_prefix, slot, out);
+ *slot_out = slot;
+ return HYBBX_OK;
+ }
+ }
+ pthread_mutex_unlock(&svc->guest_lock);
+
+ return HYBBX_ERR_BUSY;
+}
+
+void hybbx_service_guest_release(hybbx_service_t *service, unsigned slot)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+
+ if (svc == NULL || slot < 1 || slot > HYBBX_GUEST_NUMBER_MAX) {
+ return;
+ }
+
+ pthread_mutex_lock(&svc->guest_lock);
+ svc->guest_in_use[slot] = 0;
+ pthread_mutex_unlock(&svc->guest_lock);
+}
+
+hybbx_result_t hybbx_service_acquire_node(hybbx_service_t *service)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ pthread_mutex_lock(&svc->node_lock);
+ if (svc->active_nodes >= svc->max_online) {
+ pthread_mutex_unlock(&svc->node_lock);
+ return HYBBX_ERR_BUSY;
+ }
+
+ svc->active_nodes++;
+ pthread_mutex_unlock(&svc->node_lock);
+ return HYBBX_OK;
+}
+
+void hybbx_service_release_node(hybbx_service_t *service)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return;
+ }
+
+ pthread_mutex_lock(&svc->node_lock);
+ if (svc->active_nodes > 0) {
+ svc->active_nodes--;
+ }
+ pthread_mutex_unlock(&svc->node_lock);
+}
+
+hybbx_result_t hybbx_service_attach_session(hybbx_service_t *service,
+ hybbx_session_t *session)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+ hybbx_attached_session_t *node;
+
+ if (svc == NULL || session == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ node = calloc(1, sizeof(*node));
+ if (node == NULL) {
+ return HYBBX_ERR_NOMEM;
+ }
+
+ node->session = session;
+
+ pthread_mutex_lock(&svc->session_lock);
+ node->next = svc->sessions;
+ svc->sessions = node;
+ pthread_mutex_unlock(&svc->session_lock);
+
+ return HYBBX_OK;
+}
+
+void hybbx_service_detach_session(hybbx_service_t *service,
+ hybbx_session_t *session)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+ hybbx_attached_session_t *prev;
+ hybbx_attached_session_t *node;
+
+ if (svc == NULL || session == NULL) {
+ return;
+ }
+
+ pthread_mutex_lock(&svc->session_lock);
+ prev = NULL;
+ node = svc->sessions;
+ while (node != NULL) {
+ if (node->session == session) {
+ if (prev != NULL) {
+ prev->next = node->next;
+ } else {
+ svc->sessions = node->next;
+ }
+ free(node);
+ break;
+ }
+ prev = node;
+ node = node->next;
+ }
+ pthread_mutex_unlock(&svc->session_lock);
+}
+
+void hybbx_service_visit_sessions(hybbx_service_t *service,
+ hybbx_service_session_visit_fn fn,
+ void *userdata)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+ hybbx_attached_session_t *node;
+ hybbx_session_t **snapshot = NULL;
+ size_t count = 0;
+ size_t i;
+
+ if (svc == NULL || fn == NULL) {
+ return;
+ }
+
+ /*
+ * Snapshot session pointers under session_lock, then invoke callbacks
+ * without holding the lock. Visitors may write to circuit sessions,
+ * which can re-enter via load-balance -> bandwidth policy.
+ */
+ pthread_mutex_lock(&svc->session_lock);
+ for (node = svc->sessions; node != NULL; node = node->next) {
+ if (node->session != NULL) {
+ count++;
+ }
+ }
+
+ if (count > 0) {
+ snapshot = calloc(count, sizeof(*snapshot));
+ if (snapshot == NULL) {
+ hybbx_log_warn("[service] session snapshot OOM (%zu sessions) — skipped",
+ count);
+ } else {
+ i = 0;
+ for (node = svc->sessions; node != NULL; node = node->next) {
+ if (node->session != NULL) {
+ snapshot[i++] = node->session;
+ }
+ }
+ }
+ }
+ pthread_mutex_unlock(&svc->session_lock);
+
+ if (snapshot == NULL) {
+ return;
+ }
+
+ for (i = 0; i < count; i++) {
+ fn(snapshot[i], userdata);
+ }
+ free(snapshot);
+}
+
+typedef struct find_registered_session_ctx {
+ uint64_t user_id;
+ hybbx_session_t *exclude;
+ hybbx_session_t *found;
+} find_registered_session_ctx_t;
+
+static void find_registered_session_visitor(hybbx_session_t *session,
+ void *userdata)
+{
+ find_registered_session_ctx_t *ctx = (find_registered_session_ctx_t *)userdata;
+ const hybbx_session_record_t *rec;
+
+ if (ctx == NULL || ctx->found != NULL || session == NULL ||
+ session == ctx->exclude) {
+ return;
+ }
+
+ if (!hybbx_session_logged_in(session) || hybbx_session_is_guest(session)) {
+ return;
+ }
+
+ rec = hybbx_session_record(session);
+ if (rec == NULL || rec->user_id != ctx->user_id) {
+ return;
+ }
+
+ ctx->found = session;
+}
+
+hybbx_session_t *hybbx_service_find_registered_session(
+ hybbx_service_t *service,
+ uint64_t user_id,
+ hybbx_session_t *exclude)
+{
+ find_registered_session_ctx_t ctx;
+
+ if (service == NULL || user_id == 0) {
+ return NULL;
+ }
+
+ ctx.user_id = user_id;
+ ctx.exclude = exclude;
+ ctx.found = NULL;
+ hybbx_service_visit_sessions(service, find_registered_session_visitor, &ctx);
+ return ctx.found;
+}
+
+static void service_apply_service(struct hybbx_service_internal *svc,
+ const hybbx_config_t *config)
+{
+ const char *prompt;
+ const char *max_online_value;
+ const char *nodes_value;
+
+ svc->prompt[0] = '\0';
+
+ prompt = hybbx_config_get(config, "service", "prompt", NULL);
+ if (prompt != NULL && prompt[0] != '\0') {
+ hybbx_strlcpy(svc->prompt, prompt, sizeof(svc->prompt));
+ }
+
+ max_online_value = hybbx_config_get(config, "service", "max_online", NULL);
+ nodes_value = hybbx_config_get(config, "service", "nodes", NULL);
+ if (max_online_value != NULL && max_online_value[0] != '\0') {
+ svc->max_online = hybbx_config_get_uint(config, "service", "max_online",
+ HYBBX_DEFAULT_MAX_ONLINE, 1u,
+ 999u);
+ } else if (nodes_value != NULL && nodes_value[0] != '\0') {
+ svc->max_online = hybbx_config_get_uint(config, "service", "nodes",
+ HYBBX_DEFAULT_MAX_ONLINE, 1u,
+ 999u);
+ } else {
+ svc->max_online = HYBBX_DEFAULT_MAX_ONLINE;
+ }
+
+ svc->login_announce =
+ hybbx_config_get_bool(config, "service", "login_announce", 0);
+
+ hybbx_log_info("[service] max_online=%u login_announce=%s",
+ svc->max_online,
+ svc->login_announce ? "yes" : "no");
+}
+
+static int str_ieq_local(const char *a, const char *b)
+{
+ if (a == NULL || b == NULL) {
+ return 0;
+ }
+
+ while (*a != '\0' && *b != '\0') {
+ char ca = (char)(*a >= 'A' && *a <= 'Z' ? *a + 32 : *a);
+ char cb = (char)(*b >= 'A' && *b <= 'Z' ? *b + 32 : *b);
+
+ if (ca != cb) {
+ return 0;
+ }
+ a++;
+ b++;
+ }
+
+ return *a == '\0' && *b == '\0';
+}
+
+static void service_apply_texts(struct hybbx_service_internal *svc,
+ const hybbx_config_t *config)
+{
+ const char *path;
+ char resolved[HYBBX_PATH_MAX];
+
+ hybbx_texts_config_defaults(&svc->texts);
+ path = hybbx_config_get(config, "texts", "path", HYBBX_DIR_TEXT);
+ if (hybbx_path_resolve(resolved, sizeof(resolved), path) == HYBBX_OK) {
+ hybbx_strlcpy(svc->texts.path, resolved, sizeof(svc->texts.path));
+ } else if (path != NULL && path[0] != '\0') {
+ hybbx_strlcpy(svc->texts.path, path, sizeof(svc->texts.path));
+ }
+}
+
+static hybbx_storage_backend_kind_t parse_storage_backend(const char *value)
+{
+ if (value == NULL || str_ieq_local(value, "flatfile") ||
+ str_ieq_local(value, "flat") || str_ieq_local(value, "files")) {
+ return HYBBX_STORAGE_FLATFILE;
+ }
+
+ if (str_ieq_local(value, "sqlite")) {
+ return HYBBX_STORAGE_SQLITE;
+ }
+
+ if (str_ieq_local(value, "mysql")) {
+ return HYBBX_STORAGE_MYSQL;
+ }
+
+ if (str_ieq_local(value, "mariadb")) {
+ return HYBBX_STORAGE_MARIADB;
+ }
+
+ return HYBBX_STORAGE_FLATFILE;
+}
+
+static hybbx_result_t service_open_storage(struct hybbx_service_internal *svc,
+ const hybbx_config_t *config)
+{
+ hybbx_storage_options_t options;
+ hybbx_storage_sql_config_t sql_cfg;
+ const char *backend_str;
+ const char *path_raw;
+ char path_resolved[HYBBX_PATH_MAX];
+ const char *guest_prefix;
+ hybbx_result_t rc;
+
+ if (svc->storage != NULL) {
+ hybbx_storage_close(svc->storage);
+ svc->storage = NULL;
+ }
+
+ backend_str = hybbx_config_get(config, "storage", "backend", "flatfile");
+ path_raw = hybbx_config_get(config, "storage", "path", HYBBX_DEFAULT_DATA_PATH);
+
+ rc = hybbx_path_resolve(path_resolved, sizeof(path_resolved), path_raw);
+ if (rc != HYBBX_OK) {
+ hybbx_log_warn("[storage] invalid path '%s'",
+ path_raw != NULL ? path_raw : "");
+ return HYBBX_ERR_IO;
+ }
+
+ guest_prefix = hybbx_config_get(config, "auth", "guest_prefix", NULL);
+
+ options.backend = parse_storage_backend(backend_str);
+ options.path = path_resolved;
+ options.guest_prefix = guest_prefix != NULL ? guest_prefix :
+ svc->auth.guest_prefix;
+ options.sql_cfg = NULL;
+
+ if (options.backend == HYBBX_STORAGE_SQLITE) {
+ hybbx_storage_sql_config_apply(&sql_cfg, config, path_resolved);
+ options.sql_cfg = &sql_cfg;
+ }
+
+ svc->storage = hybbx_storage_open(&options);
+ if (svc->storage == NULL) {
+ hybbx_log_warn("[storage] cannot open '%s' (writable?)",
+ path_resolved);
+ return HYBBX_ERR_IO;
+ }
+
+ hybbx_log_info("[storage] backend=%s path=%s", backend_str, path_resolved);
+ return HYBBX_OK;
+}
+
+static void service_apply_auth(struct hybbx_service_internal *svc,
+ const hybbx_config_t *config)
+{
+ const char *prefix;
+
+ hybbx_auth_config_defaults(&svc->auth);
+ svc->auth.auto_login =
+ hybbx_config_get_bool(config, "auth", "auto_login", 1);
+
+ prefix = hybbx_config_get(config, "auth", "guest_prefix", NULL);
+ if (prefix != NULL && prefix[0] != '\0') {
+ hybbx_strlcpy(svc->auth.guest_prefix, prefix,
+ sizeof(svc->auth.guest_prefix));
+ }
+
+ svc->guest_timeout_minutes = hybbx_config_get_uint(
+ config, "auth", "guest_timeout_minutes",
+ HYBBX_DEFAULT_GUEST_TIMEOUT_MINUTES, 1u, 24u * 60u);
+
+ hybbx_log_info("[service] guest_timeout_minutes=%u", svc->guest_timeout_minutes);
+}
+
+static hybbx_result_t service_apply_circuit(struct hybbx_service_internal *svc,
+ const hybbx_config_t *config)
+{
+ hybbx_circuit_config_t cfg;
+ const char *bind4;
+ const char *bind6;
+ hybbx_result_t rc;
+
+ if (!hybbx_config_get_bool(config, "circuit", "enabled", 1)) {
+ if (svc->circuit_hub != NULL) {
+ hybbx_circuit_hub_stop(svc->circuit_hub);
+ }
+ return HYBBX_OK;
+ }
+
+ if (!svc->networks.circuit) {
+ if (svc->circuit_hub != NULL) {
+ hybbx_circuit_hub_stop(svc->circuit_hub);
+ }
+ return HYBBX_OK;
+ }
+
+ hybbx_circuit_config_defaults(&cfg);
+ bind4 = hybbx_config_get(config, "circuit", "bind", NULL);
+ bind6 = hybbx_config_get(config, "circuit", "bind6", NULL);
+ if (bind4 != NULL && bind4[0] != '\0') {
+ hybbx_strlcpy(cfg.bind4, bind4, sizeof(cfg.bind4));
+ }
+ if (bind6 != NULL && bind6[0] != '\0') {
+ hybbx_strlcpy(cfg.bind6, bind6, sizeof(cfg.bind6));
+ }
+
+ cfg.port = hybbx_config_get_uint(config, "circuit", "port",
+ HYBBX_CIRCUIT_DEFAULT_PORT, 1u, 65535u);
+ cfg.ipv4 = hybbx_config_get_bool(config, "circuit", "ipv4", 1);
+ cfg.ipv6 = hybbx_config_get_bool(config, "circuit", "ipv6", 1);
+ cfg.link_auth = hybbx_config_get_bool(config, "circuit", "link_auth", 1);
+ cfg.link_stale_days = hybbx_config_get_uint(
+ config, "circuit", "link_stale_days", HYBBX_LINK_STALE_DAYS, 1u, 365u);
+ cfg.balance.enabled = hybbx_config_get_bool(config, "circuit", "balance", 1);
+ cfg.balance.lag_sec = hybbx_config_get_uint(
+ config, "circuit", "balance_lag_sec", 8u, 1u, 120u);
+ cfg.balance.queue_pause = (size_t)hybbx_config_get_uint(
+ config, "circuit", "balance_queue_pause", 8192u, 256u, 1048576u);
+ cfg.balance.queue_break = (size_t)hybbx_config_get_uint(
+ config, "circuit", "balance_queue_break", 32768u, 1024u, 1048576u);
+ cfg.balance.queue_cancel = (size_t)hybbx_config_get_uint(
+ config, "circuit", "balance_queue_cancel", 131072u, 4096u, 4194304u);
+ if (cfg.balance.queue_pause > cfg.balance.queue_break) {
+ cfg.balance.queue_break = cfg.balance.queue_pause;
+ }
+ if (cfg.balance.queue_break > cfg.balance.queue_cancel) {
+ cfg.balance.queue_cancel = cfg.balance.queue_break;
+ }
+ cfg.max_links = hybbx_config_get_uint(
+ config, "circuit", "max_links", HYBBX_CIRCUIT_DEFAULT_MAX_LINKS,
+ 1u, HYBBX_CIRCUIT_MAX_LINKS);
+ (void)hybbx_circuit_bridge_load(&cfg.bridge, config);
+
+ {
+ const char *link_pw = hybbx_config_get(config, "circuit", "link_password", NULL);
+
+ if (link_pw != NULL) {
+ hybbx_strlcpy(cfg.link_password, link_pw, sizeof(cfg.link_password));
+ }
+ if (svc->storage != NULL && svc->storage->path != NULL &&
+ svc->storage->path[0] != '\0') {
+ hybbx_strlcpy(cfg.data_path, svc->storage->path, sizeof(cfg.data_path));
+ } else {
+ char data_resolved[HYBBX_PATH_MAX];
+ const char *path_raw = hybbx_config_get(config, "storage", "path",
+ HYBBX_DEFAULT_DATA_PATH);
+
+ if (hybbx_path_resolve(data_resolved, sizeof(data_resolved),
+ path_raw) == HYBBX_OK) {
+ hybbx_strlcpy(cfg.data_path, data_resolved, sizeof(cfg.data_path));
+ }
+ }
+ if (svc->config_path[0] != '\0') {
+ hybbx_strlcpy(cfg.config_path, svc->config_path, sizeof(cfg.config_path));
+ }
+ }
+
+ if (svc->circuit_hub == NULL) {
+ svc->circuit_hub = hybbx_circuit_hub_create((hybbx_service_t *)svc);
+ if (svc->circuit_hub == NULL) {
+ return HYBBX_ERR_NOMEM;
+ }
+ }
+
+ rc = hybbx_circuit_hub_start(svc->circuit_hub, &cfg);
+ return rc;
+}
+
+hybbx_result_t hybbx_service_load_transport(hybbx_service_t *service,
+ const char *plugin_name)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+ const hybbx_transport_plugin_t *plugin;
+ size_t i;
+
+ if (svc == NULL || plugin_name == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ plugin = hybbx_registry_find(plugin_name);
+ if (plugin == NULL) {
+ return HYBBX_ERR_NOT_FOUND;
+ }
+
+ for (i = 0; i < svc->transport_count; i++) {
+ if (svc->transports[i].plugin == plugin) {
+ return HYBBX_OK;
+ }
+ }
+
+ if (svc->transport_count >= HYBBX_MAX_ACTIVE_TRANSPORTS) {
+ return HYBBX_ERR_NOMEM;
+ }
+
+ if (plugin->init != NULL) {
+ hybbx_result_t rc = plugin->init(service);
+ if (rc != HYBBX_OK) {
+ return rc;
+ }
+ }
+
+ svc->transports[svc->transport_count].plugin = plugin;
+ svc->transports[svc->transport_count].running = 0;
+ svc->transport_count++;
+ return HYBBX_OK;
+}
+
+static active_transport_t *find_active(struct hybbx_service_internal *svc,
+ const char *plugin_name)
+{
+ size_t i;
+
+ for (i = 0; i < svc->transport_count; i++) {
+ if (strcmp(svc->transports[i].plugin->name, plugin_name) == 0) {
+ return &svc->transports[i];
+ }
+ }
+ return NULL;
+}
+
+hybbx_result_t hybbx_service_start_transport(hybbx_service_t *service,
+ const char *plugin_name,
+ const char *config)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+ active_transport_t *active;
+
+ if (svc == NULL || plugin_name == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ active = find_active(svc, plugin_name);
+ if (active == NULL) {
+ return HYBBX_ERR_NOT_FOUND;
+ }
+
+ if (active->running) {
+ return HYBBX_ERR_BUSY;
+ }
+
+ if (active->plugin->start == NULL) {
+ return HYBBX_ERR_UNSUPPORTED;
+ }
+
+ {
+ hybbx_result_t rc = active->plugin->start(config);
+ if (rc != HYBBX_OK) {
+ return rc;
+ }
+ }
+
+ active->running = 1;
+ return HYBBX_OK;
+}
+
+hybbx_result_t hybbx_service_stop_transport(hybbx_service_t *service,
+ const char *plugin_name)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+ active_transport_t *active;
+
+ if (svc == NULL || plugin_name == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ active = find_active(svc, plugin_name);
+ if (active == NULL) {
+ return HYBBX_ERR_NOT_FOUND;
+ }
+
+ if (!active->running) {
+ return HYBBX_OK;
+ }
+
+ if (active->plugin->stop != NULL) {
+ hybbx_result_t rc = active->plugin->stop();
+ if (rc != HYBBX_OK) {
+ return rc;
+ }
+ }
+
+ active->running = 0;
+ return HYBBX_OK;
+}
+
+hybbx_result_t hybbx_service_run(hybbx_service_t *service)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ svc->running = 1;
+
+ while (svc->running) {
+#if defined(_WIN32)
+ Sleep(1000);
+#else
+ static unsigned prune_tick;
+
+ sleep(1);
+ hybbx_security_ban_tick();
+ hybbx_monitor_tick(service);
+ hybbx_broadcast_ax25_tick(service);
+#ifdef HYBBX_HAVE_PLUGIN_MAINS_PROXY
+ hybbx_mains_proxy_plugin_tick();
+#endif
+ if (svc->storage != NULL) {
+ hybbx_storage_backup_tick(svc->storage);
+ }
+ prune_tick++;
+ if (prune_tick >= 3600u) {
+ prune_tick = 0;
+ if (svc->circuit_hub != NULL) {
+ hybbx_circuit_hub_prune_links(svc->circuit_hub);
+ }
+ }
+#endif
+ }
+
+ return HYBBX_OK;
+}
+
+void hybbx_service_stop(hybbx_service_t *service)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+
+ if (svc != NULL) {
+ svc->running = 0;
+ }
+}
+
+void hybbx_service_set_launch_binary(hybbx_service_t *service, const char *argv0)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return;
+ }
+
+ svc->launch_binary[0] = '\0';
+ if (argv0 != NULL && argv0[0] != '\0') {
+ hybbx_strlcpy(svc->launch_binary, argv0, sizeof(svc->launch_binary));
+ }
+}
+
+void hybbx_service_request_shutdown(hybbx_service_t *service, int restart)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return;
+ }
+
+ svc->shutdown_mode = restart ? HYBBX_SHUTDOWN_RESTART : HYBBX_SHUTDOWN_STOP;
+ hybbx_service_stop(service);
+}
+
+hybbx_shutdown_mode_t hybbx_service_shutdown_mode(const hybbx_service_t *service)
+{
+ const struct hybbx_service_internal *svc =
+ (const struct hybbx_service_internal *)service;
+
+ if (svc == NULL) {
+ return HYBBX_SHUTDOWN_NONE;
+ }
+
+ return svc->shutdown_mode;
+}
+
+const char *hybbx_service_config_path(const hybbx_service_t *service)
+{
+ const struct hybbx_service_internal *svc =
+ (const struct hybbx_service_internal *)service;
+
+ if (svc == NULL || svc->config_path[0] == '\0') {
+ return NULL;
+ }
+
+ return svc->config_path;
+}
+
+void hybbx_service_restart_exec(const hybbx_service_t *service)
+{
+ const struct hybbx_service_internal *svc =
+ (const struct hybbx_service_internal *)service;
+ const char *binary;
+ const char *config_path;
+ char *argv[4];
+ char opt_c[] = "-c";
+
+ if (svc == NULL) {
+ return;
+ }
+
+ binary = svc->launch_binary[0] != '\0' ? svc->launch_binary : HYBBX_DAEMON_BINARY;
+ config_path = svc->config_path[0] != '\0' ? svc->config_path : NULL;
+
+ argv[0] = (char *)binary;
+ if (config_path != NULL) {
+ argv[1] = opt_c;
+ argv[2] = (char *)config_path;
+ argv[3] = NULL;
+ } else {
+ argv[1] = NULL;
+ }
+
+ fflush(NULL);
+ execv(binary, argv);
+ perror("hybbx restart failed");
+}
+
+typedef struct apply_transport_ctx {
+ hybbx_service_t *service;
+ const hybbx_config_t *config;
+ const hybbx_networks_config_t *networks;
+ hybbx_result_t last_error;
+} apply_transport_ctx_t;
+
+static int transport_start_failure_is_fatal(const char *plugin_name)
+{
+ /* Role static transport must start; others may fail independently. */
+ return hybbx_networks_is_static_transport(plugin_name);
+}
+
+static void apply_transport_cb(const hybbx_transport_plugin_t *plugin,
+ void *userdata)
+{
+ apply_transport_ctx_t *ctx = (apply_transport_ctx_t *)userdata;
+ char section[128];
+ char *transport_config;
+ hybbx_result_t rc;
+ int transport_enabled;
+
+ if (ctx->networks == NULL ||
+ !hybbx_networks_transport_wanted(plugin->name, ctx->networks)) {
+ return;
+ }
+
+ if (!hybbx_config_resolve_transport_section(ctx->config, plugin->name,
+ section, sizeof(section))) {
+ snprintf(section, sizeof(section), "transport.%s", plugin->name);
+ }
+
+ if (hybbx_networks_is_static_transport(plugin->name)) {
+ transport_enabled = 1;
+ } else {
+ transport_enabled = hybbx_config_get_bool(ctx->config, section,
+ "enabled", 1);
+ }
+
+ if (!transport_enabled) {
+ return;
+ }
+
+ rc = hybbx_service_load_transport(ctx->service, plugin->name);
+ if (rc != HYBBX_OK) {
+ hybbx_log_warn("[service] %s: plugin load failed (%s)",
+ plugin->name, hybbx_result_name(rc));
+ if (transport_start_failure_is_fatal(plugin->name)) {
+ ctx->last_error = rc;
+ }
+ return;
+ }
+
+ if (strcmp(plugin->name, "packet_radio") == 0) {
+ transport_config =
+ hybbx_config_format_packet_radio_start(ctx->config);
+ } else if (strcmp(plugin->name, "baycom") == 0) {
+ transport_config = hybbx_config_format_baycom_start(ctx->config);
+ } else if (strcmp(plugin->name, "mains_proxy") == 0) {
+ transport_config = hybbx_config_format_transport_sections(ctx->config,
+ plugin->name);
+ } else {
+ transport_config = NULL;
+ }
+ if (transport_config == NULL) {
+ transport_config = hybbx_config_format_section(ctx->config, section);
+ }
+ rc = hybbx_service_start_transport(ctx->service, plugin->name,
+ transport_config);
+ free(transport_config);
+
+ if (rc != HYBBX_OK) {
+ hybbx_log_warn("[service] %s: start failed (%s)", plugin->name,
+ hybbx_result_name(rc));
+ if (transport_start_failure_is_fatal(plugin->name)) {
+ ctx->last_error = rc;
+ }
+ }
+}
+
+hybbx_result_t hybbx_service_apply_config(hybbx_service_t *service,
+ const hybbx_config_t *config,
+ const char *config_path)
+{
+ struct hybbx_service_internal *svc =
+ (struct hybbx_service_internal *)service;
+ const char *service_name;
+ apply_transport_ctx_t ctx;
+ char *new_name;
+ hybbx_result_t rc;
+
+ if (svc == NULL || config == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ svc->config_path[0] = '\0';
+ if (config_path != NULL && config_path[0] != '\0') {
+ hybbx_strlcpy(svc->config_path, config_path, sizeof(svc->config_path));
+ }
+
+ service_name = hybbx_config_get(config, "service", "name", NULL);
+ if (service_name != NULL) {
+ new_name = hybbx_strdup(service_name);
+ if (new_name == NULL) {
+ return HYBBX_ERR_NOMEM;
+ }
+ free(svc->name);
+ svc->name = new_name;
+ }
+
+ service_apply_auth(svc, config);
+ hybbx_time_config_apply(config);
+ hybbx_log_config_apply(config);
+ hybbx_security_log_config_apply(config);
+ hybbx_monitor_config_apply(config);
+ hybbx_security_ban_config_apply(config);
+ service_apply_texts(svc, config);
+ hybbx_chat_config_apply(&svc->chat, config);
+ hybbx_broadcast_config_apply(&svc->broadcast, config);
+ service_apply_service(svc, config);
+ hybbx_crypto_config_apply(config);
+ hybbx_traffic_config_apply(config);
+
+ rc = service_open_storage(svc, config);
+ if (rc != HYBBX_OK) {
+ return rc;
+ }
+
+ {
+ const char *storage_path = HYBBX_DEFAULT_DATA_PATH;
+ char storage_resolved[HYBBX_PATH_MAX];
+
+ if (svc->storage != NULL && svc->storage->path != NULL &&
+ svc->storage->path[0] != '\0') {
+ storage_path = svc->storage->path;
+ } else if (hybbx_path_resolve(storage_resolved, sizeof(storage_resolved),
+ storage_path) == HYBBX_OK) {
+ storage_path = storage_resolved;
+ }
+ hybbx_mail_config_apply(&svc->mail, config, storage_path);
+ }
+
+ hybbx_networks_config_apply(&svc->networks, config);
+
+ {
+ int standalone = hybbx_config_get_bool(config, "instance", "standalone", 0);
+
+ if (!standalone && hybbx_instance_role() == HYBBX_INSTANCE_MAIN) {
+ if (svc->networks.ax25 || svc->networks.baycom ||
+ svc->networks.ardop || svc->networks.crdop) {
+ standalone = 1;
+ }
+ }
+ hybbx_instance_set_standalone(standalone);
+ }
+
+ rc = hybbx_networks_enforce_instance(&svc->networks);
+ if (rc != HYBBX_OK) {
+ return rc;
+ }
+
+ rc = service_apply_circuit(svc, config);
+ if (rc != HYBBX_OK) {
+ return rc;
+ }
+
+ ctx.service = service;
+ ctx.config = config;
+ ctx.networks = &svc->networks;
+ ctx.last_error = HYBBX_OK;
+
+ hybbx_registry_foreach(apply_transport_cb, &ctx);
+ return ctx.last_error;
+}
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com