From cfacedbb1adc9ab6f9ee3c7a5ce4eddffabe6455 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 2 Oct 2025 11:38:28 -0400 Subject: [PATCH] v0.4.2 - Implement NIP-11 Relay Information Document with event-based configuration - make relay info dynamically configurable via admin API --- FIX_ME.md | 94 ------- Makefile | 118 ++++++--- README.md | 8 + build_and_push.sh | 6 +- ...03a9d11057e9afabf61a89cd5a31375c96eacde.db | Bin 155648 -> 0 bytes relay.pid | 2 +- src/config.c | 191 ++++++++++---- src/config.h | 5 + src/default_config_event.h | 15 +- src/main.h | 32 +++ src/nip011.c | 232 +++++++++++++++--- src/nip013.c | 1 + src/request_validator.c | 23 +- 13 files changed, 497 insertions(+), 230 deletions(-) delete mode 100644 FIX_ME.md delete mode 100644 e1d3c52a4a330293e4de1327403a9d11057e9afabf61a89cd5a31375c96eacde.db create mode 100644 src/main.h diff --git a/FIX_ME.md b/FIX_ME.md deleted file mode 100644 index e5b72eb..0000000 --- a/FIX_ME.md +++ /dev/null @@ -1,94 +0,0 @@ -Inconsistency audit with exact fixes (treating README.md as authoritative) - -Backend auth_rules schema mismatch -Evidence: -Migration (creates mismatched columns): -See "CREATE TABLE IF NOT EXISTS auth_rules ... UNIQUE(rule_type, operation, rule_target)". -Active code uses rule_type, pattern_type, pattern_value, action: -Insert: "INSERT INTO auth_rules (rule_type, pattern_type, pattern_value, action)" -Delete: "DELETE FROM auth_rules WHERE rule_type = ? AND pattern_type = ? AND pattern_value = ?" -Query mapping: map_auth_query_type_to_response() -Queries: "... WHERE rule_type LIKE '%blacklist%'" -Validator checks: -"... WHERE rule_type = 'blacklist' AND pattern_type = 'pubkey' AND pattern_value = ?" -"... WHERE rule_type = 'blacklist' AND pattern_type = 'hash' AND pattern_value = ?" -"... WHERE rule_type = 'whitelist' AND pattern_type = 'pubkey' AND pattern_value = ?" -Embedded schema expects pattern columns and active/indexes: -"CREATE TABLE auth_rules ( ... )" -"CREATE INDEX idx_auth_rules_pattern ON auth_rules(pattern_type, pattern_value)" -"CREATE INDEX idx_auth_rules_active ON auth_rules(active)" -Fix (update migration to align with sql_schema.h/config.c): -Replace the DDL at "create_auth_rules_sql" with: CREATE TABLE IF NOT EXISTS auth_rules ( id INTEGER PRIMARY KEY AUTOINCREMENT, rule_type TEXT NOT NULL, -- 'whitelist' | 'blacklist' pattern_type TEXT NOT NULL, -- 'pubkey' | 'hash' | future pattern_value TEXT NOT NULL, -- hex pubkey/hash action TEXT NOT NULL, -- 'allow' | 'deny' active INTEGER DEFAULT 1, created_at INTEGER DEFAULT (strftime('%s','now')), UNIQUE(rule_type, pattern_type, pattern_value) ); -After creation, also create indexes as in "sql_schema.h": -CREATE INDEX idx_auth_rules_pattern ON auth_rules(pattern_type, pattern_value); -CREATE INDEX idx_auth_rules_type ON auth_rules(rule_type); -CREATE INDEX idx_auth_rules_active ON auth_rules(active); -Duplicate UI function + stale DOM id usage -Evidence: -Duplicate definition of disconnectFromRelay() and disconnectFromRelay(); the second overwrites the first and uses legacy element access paths. -Stale variable: "const relayUrl = document.getElementById('relay-url');" — no element with id="relay-url" exists; the real input is "relay-connection-url" and is referenced as "relayConnectionUrl". -Calls using relayUrl.value.trim() (must use relayConnectionUrl): -"sendConfigUpdateCommand() publish URL" -"loadAuthRules() publish URL" -"deleteAuthRule() publish URL" -Tests: -"testGetAuthRules()" -"testClearAuthRules()" -"testAddBlacklist()" -"testAddWhitelist()" -"testConfigQuery()" -"testPostEvent()" -Fix: -Remove the duplicate legacy function entirely: delete the second disconnectFromRelay(). -Remove stale variable: delete "const relayUrl = document.getElementById('relay-url');". -Replace every relayUrl.value.trim() occurrence with relayConnectionUrl.value.trim() at the lines listed above. -Supported NIPs inconsistency (README vs UI fallback) -Evidence: -README implemented NIPs checklist (authoritative): "NIPs list" shows: 1, 9, 11, 13, 15, 20, 33, 40, 42 implemented. -UI fallback for manual relay info includes unsupported/undocumented NIPs and misses implemented ones: -"supported_nips: [1, 2, 4, 9, 11, 12, 15, 16, 20, 22]" -"supported_nips: [1, 2, 4, 9, 11, 12, 15, 16, 20, 22]" -Fix: -Replace both arrays with: [1, 9, 11, 13, 15, 20, 33, 40, 42] -Config key mismatches (README vs UI edit form) -Evidence: -README keys (authoritative): "Available Configuration Keys"–(README.md:110) -relay_description, relay_contact, max_connections, max_subscriptions_per_client, max_event_tags, max_content_length, auth_enabled, nip42_auth_required, nip42_auth_required_kinds, nip42_challenge_timeout, pow_min_difficulty, nip40_expiration_enabled -UI currently declares/uses many non-README keys: -Field types: "fieldTypes" include nip42_auth_required_events, nip42_auth_required_subscriptions, relay_port, pow_mode, nip40_expiration_strict, nip40_expiration_filter, nip40_expiration_grace_period, max_total_subscriptions, max_filters_per_subscription, max_message_length, default_limit, max_limit. -Descriptions: "descriptions" reflect the same non-README keys. -Fix: -Restrict UI form generation to README keys and rename mismatches: -Combine nip42_auth_required_events/subscriptions into README’s "nip42_auth_required" (boolean). -Rename nip42_challenge_expiration to "nip42_challenge_timeout". -Remove or hide (advanced section) non-README keys: relay_port, pow_mode, nip40_expiration_strict, nip40_expiration_filter, nip40_expiration_grace_period, max_total_subscriptions, max_filters_per_subscription, max_message_length, default_limit, max_limit. -Update both "fieldTypes" and "descriptions" to reflect only README keys (data types and labels consistent). -First-time startup port override (-p) ignored when -a and -r are also provided -Observation: -You confirmed: first run with -p 7777 works, but with -p plus -a and -r the override isn’t honored. -Likely cause: -The code path that handles admin/relay key overrides on first-time setup bypasses persisting the CLI port override to config/unified cache before server start, so "start_websocket_relay(-1, ...)" falls back to default. -Fix: -Ensure first_time_startup_sequence applies cli_options.port_override to persistent config and cache BEFORE default config insertion and before starting the server. Specifically: -In the first-time path (main): -After "first_time_startup_sequence(&cli_options)" and before creating defaults on the -a/-r path at "populate_default_config_values()", write the port override: -set_config_value_in_table("relay_port", "", "integer", "WebSocket port", "relay", 0); -and update unified cache if required by the port resolution code. -Verify the code path where -a/-r trigger direct table population also applies/overwrites the port with the CLI-provided value. -Add a regression test to assert that -p is honored with and without -a/-r on first run. -Minor consistency recommendations -UI NIP-11 fallback version string: -Consider aligning with backend version source (e.g., src/version.h). The UI currently hardcodes "1.0.0" at "version: '1.0.0'". -UI hardcoded relay pubkey fallback: -"getRelayPubkey()" returns a constant when not connected. Safe for dev, but should not leak into production paths. -Added TODO items (as requested) - -The following todos were added/organized: - Remove duplicate disconnectFromRelay() and standardize to relay-connection-url - Replace all relayUrl.value references with relayConnectionUrl.value in api/index.html - Align Supported NIPs fallback arrays in api/index.html with README (1,9,11,13,15,20,33,40,42) - Update config form keys/descriptions in api/index.html to match README keys - Fix backend auth_rules migration in src/main.c to match src/sql_schema.h/src/config.c - Investigate and fix first-time startup port override ignored when -a and -r are provided - Add tests for port override and auth_rules flows - Rebuild via ./make_and_restart_relay.sh and validate against README diff --git a/Makefile b/Makefile index d407639..ad29d47 100644 --- a/Makefile +++ b/Makefile @@ -36,10 +36,10 @@ $(NOSTR_CORE_LIB): @echo "Building nostr_core_lib..." cd nostr_core_lib && ./build.sh -# Generate version.h from git tags -src/version.h: +# Generate main.h from git tags +src/main.h: @if [ -d .git ]; then \ - echo "Generating version.h from git tags..."; \ + echo "Generating main.h from git tags..."; \ RAW_VERSION=$$(git describe --tags --always 2>/dev/null || echo "unknown"); \ if echo "$$RAW_VERSION" | grep -q "^v[0-9]"; then \ CLEAN_VERSION=$$(echo "$$RAW_VERSION" | sed 's/^v//' | cut -d- -f1); \ @@ -51,54 +51,98 @@ src/version.h: VERSION="v0.0.0"; \ MAJOR=0; MINOR=0; PATCH=0; \ fi; \ - echo "/* Auto-generated version information */" > src/version.h; \ - echo "#ifndef VERSION_H" >> src/version.h; \ - echo "#define VERSION_H" >> src/version.h; \ - echo "" >> src/version.h; \ - echo "#define VERSION \"$$VERSION\"" >> src/version.h; \ - echo "#define VERSION_MAJOR $$MAJOR" >> src/version.h; \ - echo "#define VERSION_MINOR $$MINOR" >> src/version.h; \ - echo "#define VERSION_PATCH $$PATCH" >> src/version.h; \ - echo "" >> src/version.h; \ - echo "#endif /* VERSION_H */" >> src/version.h; \ - echo "Generated version.h with clean version: $$VERSION"; \ - elif [ ! -f src/version.h ]; then \ - echo "Git not available and version.h missing, creating fallback version.h..."; \ + echo "/*" > src/main.h; \ + echo " * C-Relay Main Header - Version and Metadata Information" >> src/main.h; \ + echo " *" >> src/main.h; \ + echo " * This header contains version information and relay metadata that is" >> src/main.h; \ + echo " * automatically updated by the build system (build_and_push.sh)." >> src/main.h; \ + echo " *" >> src/main.h; \ + echo " * The build_and_push.sh script updates VERSION and related macros when" >> src/main.h; \ + echo " * creating new releases." >> src/main.h; \ + echo " */" >> src/main.h; \ + echo "" >> src/main.h; \ + echo "#ifndef MAIN_H" >> src/main.h; \ + echo "#define MAIN_H" >> src/main.h; \ + echo "" >> src/main.h; \ + echo "// Version information (auto-updated by build_and_push.sh)" >> src/main.h; \ + echo "#define VERSION \"$$VERSION\"" >> src/main.h; \ + echo "#define VERSION_MAJOR $$MAJOR" >> src/main.h; \ + echo "#define VERSION_MINOR $$MINOR" >> src/main.h; \ + echo "#define VERSION_PATCH $$PATCH" >> src/main.h; \ + echo "" >> src/main.h; \ + echo "// Relay metadata (authoritative source for NIP-11 information)" >> src/main.h; \ + echo "#define RELAY_NAME \"C-Relay\"" >> src/main.h; \ + echo "#define RELAY_DESCRIPTION \"High-performance C Nostr relay with SQLite storage\"" >> src/main.h; \ + echo "#define RELAY_CONTACT \"\"" >> src/main.h; \ + echo "#define RELAY_SOFTWARE \"https://git.laantungir.net/laantungir/c-relay.git\"" >> src/main.h; \ + echo "#define RELAY_VERSION VERSION // Use the same version as the build" >> src/main.h; \ + echo "#define SUPPORTED_NIPS \"1,2,4,9,11,12,13,15,16,20,22,33,40,42\"" >> src/main.h; \ + echo "#define LANGUAGE_TAGS \"\"" >> src/main.h; \ + echo "#define RELAY_COUNTRIES \"\"" >> src/main.h; \ + echo "#define POSTING_POLICY \"\"" >> src/main.h; \ + echo "#define PAYMENTS_URL \"\"" >> src/main.h; \ + echo "" >> src/main.h; \ + echo "#endif /* MAIN_H */" >> src/main.h; \ + echo "Generated main.h with clean version: $$VERSION"; \ + elif [ ! -f src/main.h ]; then \ + echo "Git not available and main.h missing, creating fallback main.h..."; \ VERSION="v0.0.0"; \ - echo "/* Auto-generated version information */" > src/version.h; \ - echo "#ifndef VERSION_H" >> src/version.h; \ - echo "#define VERSION_H" >> src/version.h; \ - echo "" >> src/version.h; \ - echo "#define VERSION \"$$VERSION\"" >> src/version.h; \ - echo "#define VERSION_MAJOR 0" >> src/version.h; \ - echo "#define VERSION_MINOR 0" >> src/version.h; \ - echo "#define VERSION_PATCH 0" >> src/version.h; \ - echo "" >> src/version.h; \ - echo "#endif /* VERSION_H */" >> src/version.h; \ - echo "Created fallback version.h with version: $$VERSION"; \ + echo "/*" > src/main.h; \ + echo " * C-Relay Main Header - Version and Metadata Information" >> src/main.h; \ + echo " *" >> src/main.h; \ + echo " * This header contains version information and relay metadata that is" >> src/main.h; \ + echo " * automatically updated by the build system (build_and_push.sh)." >> src/main.h; \ + echo " *" >> src/main.h; \ + echo " * The build_and_push.sh script updates VERSION and related macros when" >> src/main.h; \ + echo " * creating new releases." >> src/main.h; \ + echo " */" >> src/main.h; \ + echo "" >> src/main.h; \ + echo "#ifndef MAIN_H" >> src/main.h; \ + echo "#define MAIN_H" >> src/main.h; \ + echo "" >> src/main.h; \ + echo "// Version information (auto-updated by build_and_push.sh)" >> src/main.h; \ + echo "#define VERSION \"$$VERSION\"" >> src/main.h; \ + echo "#define VERSION_MAJOR 0" >> src/main.h; \ + echo "#define VERSION_MINOR 0" >> src/main.h; \ + echo "#define VERSION_PATCH 0" >> src/main.h; \ + echo "" >> src/main.h; \ + echo "// Relay metadata (authoritative source for NIP-11 information)" >> src/main.h; \ + echo "#define RELAY_NAME \"C-Relay\"" >> src/main.h; \ + echo "#define RELAY_DESCRIPTION \"High-performance C Nostr relay with SQLite storage\"" >> src/main.h; \ + echo "#define RELAY_CONTACT \"\"" >> src/main.h; \ + echo "#define RELAY_SOFTWARE \"https://git.laantungir.net/laantungir/c-relay.git\"" >> src/main.h; \ + echo "#define RELAY_VERSION VERSION // Use the same version as the build" >> src/main.h; \ + echo "#define SUPPORTED_NIPS \"1,2,4,9,11,12,13,15,16,20,22,33,40,42\"" >> src/main.h; \ + echo "#define LANGUAGE_TAGS \"\"" >> src/main.h; \ + echo "#define RELAY_COUNTRIES \"\"" >> src/main.h; \ + echo "#define POSTING_POLICY \"\"" >> src/main.h; \ + echo "#define PAYMENTS_URL \"\"" >> src/main.h; \ + echo "" >> src/main.h; \ + echo "#endif /* MAIN_H */" >> src/main.h; \ + echo "Created fallback main.h with version: $$VERSION"; \ else \ - echo "Git not available, preserving existing version.h"; \ + echo "Git not available, preserving existing main.h"; \ fi -# Force version.h regeneration (useful for development) +# Force main.h regeneration (useful for development) force-version: - @echo "Force regenerating version.h..." - @rm -f src/version.h - @$(MAKE) src/version.h + @echo "Force regenerating main.h..." + @rm -f src/main.h + @$(MAKE) src/main.h # Build the relay -$(TARGET): $(BUILD_DIR) src/version.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB) +$(TARGET): $(BUILD_DIR) src/main.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB) @echo "Compiling C-Relay for architecture: $(ARCH)" $(CC) $(CFLAGS) $(INCLUDES) $(MAIN_SRC) -o $(TARGET) $(NOSTR_CORE_LIB) $(LIBS) @echo "Build complete: $(TARGET)" # Build for specific architectures -x86: $(BUILD_DIR) src/version.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB) +x86: $(BUILD_DIR) src/main.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB) @echo "Building C-Relay for x86_64..." $(CC) $(CFLAGS) $(INCLUDES) $(MAIN_SRC) -o $(BUILD_DIR)/c_relay_x86 $(NOSTR_CORE_LIB) $(LIBS) @echo "Build complete: $(BUILD_DIR)/c_relay_x86" -arm64: $(BUILD_DIR) src/version.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB) +arm64: $(BUILD_DIR) src/main.h src/sql_schema.h $(MAIN_SRC) $(NOSTR_CORE_LIB) @echo "Cross-compiling C-Relay for ARM64..." @if ! command -v aarch64-linux-gnu-gcc >/dev/null 2>&1; then \ echo "ERROR: ARM64 cross-compiler not found."; \ @@ -171,7 +215,7 @@ init-db: # Clean build artifacts clean: rm -rf $(BUILD_DIR) - rm -f src/version.h + rm -f src/main.h @echo "Clean complete" # Clean everything including nostr_core_lib @@ -210,6 +254,6 @@ help: @echo " make check-toolchain # Check what compilers are available" @echo " make test # Run tests" @echo " make init-db # Set up database" - @echo " make force-version # Force regenerate version.h from git" + @echo " make force-version # Force regenerate main.h from git" .PHONY: all x86 arm64 test init-db clean clean-all install-deps install-cross-tools install-arm64-deps check-toolchain help force-version \ No newline at end of file diff --git a/README.md b/README.md index 077a08c..f2b04ab 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,16 @@ All commands are sent as NIP-44 encrypted JSON arrays in the event content. The ### Available Configuration Keys **Basic Relay Settings:** +- `relay_name`: Relay name (displayed in NIP-11) - `relay_description`: Relay description text - `relay_contact`: Contact information +- `relay_software`: Software URL +- `relay_version`: Software version +- `supported_nips`: Comma-separated list of supported NIP numbers (e.g., "1,2,4,9,11,12,13,15,16,20,22,33,40,42") +- `language_tags`: Comma-separated list of supported language tags (e.g., "en,es,fr" or "*" for all) +- `relay_countries`: Comma-separated list of supported country codes (e.g., "US,CA,MX" or "*" for all) +- `posting_policy`: Posting policy URL or text +- `payments_url`: Payment URL for premium features - `max_connections`: Maximum concurrent connections - `max_subscriptions_per_client`: Max subscriptions per client - `max_event_tags`: Maximum tags per event diff --git a/build_and_push.sh b/build_and_push.sh index 2c28bd4..afe8f01 100755 --- a/build_and_push.sh +++ b/build_and_push.sh @@ -139,11 +139,11 @@ compile_project() { print_warning "Clean failed or no Makefile found" fi - # Force regenerate version.h to pick up new tags + # Force regenerate main.h to pick up new tags if make force-version > /dev/null 2>&1; then - print_success "Regenerated version.h" + print_success "Regenerated main.h" else - print_warning "Failed to regenerate version.h" + print_warning "Failed to regenerate main.h" fi # Compile the project diff --git a/e1d3c52a4a330293e4de1327403a9d11057e9afabf61a89cd5a31375c96eacde.db b/e1d3c52a4a330293e4de1327403a9d11057e9afabf61a89cd5a31375c96eacde.db deleted file mode 100644 index d1c5ad40bfe5534e47043992701374b60faf4200..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155648 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 0) ? timeout : 300; // Use provided value or default } - + return 300; // Default 5 minutes } +// Helper function to safely return dynamically allocated string from static buffer +static char* safe_strdup_from_static(const char* static_str) { + if (!static_str) return NULL; + return strdup(static_str); +} + // Force cache refresh - invalidates current cache void force_config_cache_refresh(void) { pthread_mutex_lock(&g_unified_cache.cache_lock); @@ -396,58 +402,61 @@ const char* get_config_value(const char* key) { if (!key) { return NULL; } - + // Special fast path for frequently accessed keys via unified cache if (strcmp(key, "admin_pubkey") == 0) { - return get_admin_pubkey_cached(); + const char* cached_value = get_admin_pubkey_cached(); + return safe_strdup_from_static(cached_value); } if (strcmp(key, "relay_pubkey") == 0) { - return get_relay_pubkey_cached(); + const char* cached_value = get_relay_pubkey_cached(); + return safe_strdup_from_static(cached_value); } - + // For other keys, try config table first const char* table_value = get_config_value_from_table(key); if (table_value) { return table_value; } - + // Fallback to legacy event-based config for backward compatibility // Use unified cache buffer instead of static buffer - + if (!g_current_config) { return NULL; } - + // Look for key in current configuration tags cJSON* tags = cJSON_GetObjectItem(g_current_config, "tags"); if (!tags || !cJSON_IsArray(tags)) { return NULL; } - + pthread_mutex_lock(&g_unified_cache.cache_lock); - + cJSON* tag = NULL; cJSON_ArrayForEach(tag, tags) { if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) { cJSON* tag_key = cJSON_GetArrayItem(tag, 0); cJSON* tag_value = cJSON_GetArrayItem(tag, 1); - + if (tag_key && tag_value && cJSON_IsString(tag_key) && cJSON_IsString(tag_value)) { - + if (strcmp(cJSON_GetStringValue(tag_key), key) == 0) { strncpy(g_unified_cache.temp_buffer, cJSON_GetStringValue(tag_value), sizeof(g_unified_cache.temp_buffer) - 1); g_unified_cache.temp_buffer[sizeof(g_unified_cache.temp_buffer) - 1] = '\0'; + const char* result = safe_strdup_from_static(g_unified_cache.temp_buffer); pthread_mutex_unlock(&g_unified_cache.cache_lock); - return g_unified_cache.temp_buffer; + return result; } } } } - + pthread_mutex_unlock(&g_unified_cache.cache_lock); - + return NULL; } @@ -456,14 +465,18 @@ int get_config_int(const char* key, int default_value) { if (!str_value) { return default_value; } - + char* endptr; long val = strtol(str_value, &endptr, 10); - + if (endptr == str_value || *endptr != '\0') { + // Free the dynamically allocated string + free((char*)str_value); return default_value; } - + + // Free the dynamically allocated string + free((char*)str_value); return (int)val; } @@ -472,18 +485,23 @@ int get_config_bool(const char* key, int default_value) { if (!str_value) { return default_value; } - - if (strcasecmp(str_value, "true") == 0 || - strcasecmp(str_value, "yes") == 0 || + + int result; + if (strcasecmp(str_value, "true") == 0 || + strcasecmp(str_value, "yes") == 0 || strcasecmp(str_value, "1") == 0) { - return 1; - } else if (strcasecmp(str_value, "false") == 0 || - strcasecmp(str_value, "no") == 0 || + result = 1; + } else if (strcasecmp(str_value, "false") == 0 || + strcasecmp(str_value, "no") == 0 || strcasecmp(str_value, "0") == 0) { - return 0; + result = 0; + } else { + result = default_value; } - - return default_value; + + // Free the dynamically allocated string + free((char*)str_value); + return result; } // ================================ @@ -1063,33 +1081,38 @@ int startup_existing_relay(const char* relay_pubkey) { log_error("Invalid relay pubkey for existing relay startup"); return -1; } - + log_info("Starting existing relay..."); printf(" Relay pubkey: %s\n", relay_pubkey); - + // Store relay pubkey in unified cache pthread_mutex_lock(&g_unified_cache.cache_lock); strncpy(g_unified_cache.relay_pubkey, relay_pubkey, sizeof(g_unified_cache.relay_pubkey) - 1); g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0'; pthread_mutex_unlock(&g_unified_cache.cache_lock); - + // Set database path char* db_name = get_database_name_from_relay_pubkey(relay_pubkey); if (!db_name) { log_error("Failed to generate database name"); return -1; } - + strncpy(g_database_path, db_name, sizeof(g_database_path) - 1); g_database_path[sizeof(g_database_path) - 1] = '\0'; free(db_name); - + + // Ensure default configuration values are populated (for any missing keys) + if (populate_default_config_values() != 0) { + log_warning("Failed to populate default config values for existing relay - continuing"); + } + // Configuration will be migrated from events to table after database initialization log_info("Configuration migration will be performed after database is available"); - + // Load configuration event from database (after database is initialized) // This will be done in apply_configuration_from_database() - + log_success("Existing relay startup prepared"); return 0; } @@ -1814,31 +1837,90 @@ int handle_configuration_event(cJSON* event, char* error_message, size_t error_s // Get value from config table const char* get_config_value_from_table(const char* key) { if (!g_db || !key) return NULL; - + const char* sql = "SELECT value FROM config WHERE key = ?"; - + sqlite3_stmt* stmt; int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { return NULL; } - + sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC); - + const char* result = NULL; - + if (sqlite3_step(stmt) == SQLITE_ROW) { const char* value = (char*)sqlite3_column_text(stmt, 0); if (value) { - // Use unified cache buffer with thread safety - pthread_mutex_lock(&g_unified_cache.cache_lock); - strncpy(g_unified_cache.temp_buffer, value, sizeof(g_unified_cache.temp_buffer) - 1); - g_unified_cache.temp_buffer[sizeof(g_unified_cache.temp_buffer) - 1] = '\0'; - result = g_unified_cache.temp_buffer; - pthread_mutex_unlock(&g_unified_cache.cache_lock); + // For NIP-11 fields, store in cache buffers but return dynamically allocated strings for consistency + if (strcmp(key, "relay_name") == 0) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + strncpy(g_unified_cache.relay_info.name, value, sizeof(g_unified_cache.relay_info.name) - 1); + g_unified_cache.relay_info.name[sizeof(g_unified_cache.relay_info.name) - 1] = '\0'; + result = strdup(value); // Return dynamically allocated copy + pthread_mutex_unlock(&g_unified_cache.cache_lock); + } else if (strcmp(key, "relay_description") == 0) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + strncpy(g_unified_cache.relay_info.description, value, sizeof(g_unified_cache.relay_info.description) - 1); + g_unified_cache.relay_info.description[sizeof(g_unified_cache.relay_info.description) - 1] = '\0'; + result = strdup(value); // Return dynamically allocated copy + pthread_mutex_unlock(&g_unified_cache.cache_lock); + } else if (strcmp(key, "relay_contact") == 0) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + strncpy(g_unified_cache.relay_info.contact, value, sizeof(g_unified_cache.relay_info.contact) - 1); + g_unified_cache.relay_info.contact[sizeof(g_unified_cache.relay_info.contact) - 1] = '\0'; + result = strdup(value); // Return dynamically allocated copy + pthread_mutex_unlock(&g_unified_cache.cache_lock); + } else if (strcmp(key, "relay_software") == 0) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + strncpy(g_unified_cache.relay_info.software, value, sizeof(g_unified_cache.relay_info.software) - 1); + g_unified_cache.relay_info.software[sizeof(g_unified_cache.relay_info.software) - 1] = '\0'; + result = strdup(value); // Return dynamically allocated copy + pthread_mutex_unlock(&g_unified_cache.cache_lock); + } else if (strcmp(key, "relay_version") == 0) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + strncpy(g_unified_cache.relay_info.version, value, sizeof(g_unified_cache.relay_info.version) - 1); + g_unified_cache.relay_info.version[sizeof(g_unified_cache.relay_info.version) - 1] = '\0'; + result = strdup(value); // Return dynamically allocated copy + pthread_mutex_unlock(&g_unified_cache.cache_lock); + } else if (strcmp(key, "supported_nips") == 0) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + strncpy(g_unified_cache.relay_info.supported_nips_str, value, sizeof(g_unified_cache.relay_info.supported_nips_str) - 1); + g_unified_cache.relay_info.supported_nips_str[sizeof(g_unified_cache.relay_info.supported_nips_str) - 1] = '\0'; + result = strdup(value); // Return dynamically allocated copy + pthread_mutex_unlock(&g_unified_cache.cache_lock); + } else if (strcmp(key, "language_tags") == 0) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + strncpy(g_unified_cache.relay_info.language_tags_str, value, sizeof(g_unified_cache.relay_info.language_tags_str) - 1); + g_unified_cache.relay_info.language_tags_str[sizeof(g_unified_cache.relay_info.language_tags_str) - 1] = '\0'; + result = strdup(value); // Return dynamically allocated copy + pthread_mutex_unlock(&g_unified_cache.cache_lock); + } else if (strcmp(key, "relay_countries") == 0) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + strncpy(g_unified_cache.relay_info.relay_countries_str, value, sizeof(g_unified_cache.relay_info.relay_countries_str) - 1); + g_unified_cache.relay_info.relay_countries_str[sizeof(g_unified_cache.relay_info.relay_countries_str) - 1] = '\0'; + result = strdup(value); // Return dynamically allocated copy + pthread_mutex_unlock(&g_unified_cache.cache_lock); + } else if (strcmp(key, "posting_policy") == 0) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + strncpy(g_unified_cache.relay_info.posting_policy, value, sizeof(g_unified_cache.relay_info.posting_policy) - 1); + g_unified_cache.relay_info.posting_policy[sizeof(g_unified_cache.relay_info.posting_policy) - 1] = '\0'; + result = strdup(value); // Return dynamically allocated copy + pthread_mutex_unlock(&g_unified_cache.cache_lock); + } else if (strcmp(key, "payments_url") == 0) { + pthread_mutex_lock(&g_unified_cache.cache_lock); + strncpy(g_unified_cache.relay_info.payments_url, value, sizeof(g_unified_cache.relay_info.payments_url) - 1); + g_unified_cache.relay_info.payments_url[sizeof(g_unified_cache.relay_info.payments_url) - 1] = '\0'; + result = strdup(value); // Return dynamically allocated copy + pthread_mutex_unlock(&g_unified_cache.cache_lock); + } else { + // For other keys, return a dynamically allocated string to prevent buffer reuse + result = strdup(value); + } } } - + sqlite3_finalize(stmt); return result; } @@ -3906,12 +3988,17 @@ const char* get_config_value_hybrid(const char* key) { if (is_config_table_ready()) { const char* table_value = get_config_value_from_table(key); if (table_value) { - return table_value; + return table_value; // Already dynamically allocated } } - - // Fall back to event-based config - return get_config_value(key); + + // Fall back to event-based config, but ensure it's dynamically allocated + const char* fallback_value = get_config_value(key); + if (fallback_value) { + return strdup(fallback_value); // Make a copy since fallback might be static + } + + return NULL; } // Check if config table is ready diff --git a/src/config.h b/src/config.h index bc7504b..e300f2e 100644 --- a/src/config.h +++ b/src/config.h @@ -56,6 +56,11 @@ typedef struct { char version[64]; char privacy_policy[RELAY_URL_MAX_LENGTH]; char terms_of_service[RELAY_URL_MAX_LENGTH]; + // Raw string values for parsing into cJSON arrays + char supported_nips_str[CONFIG_VALUE_MAX_LENGTH]; + char language_tags_str[CONFIG_VALUE_MAX_LENGTH]; + char relay_countries_str[CONFIG_VALUE_MAX_LENGTH]; + // Parsed cJSON arrays cJSON* supported_nips; cJSON* limitation; cJSON* retention; diff --git a/src/default_config_event.h b/src/default_config_event.h index f857548..6687773 100644 --- a/src/default_config_event.h +++ b/src/default_config_event.h @@ -3,6 +3,7 @@ #include #include "config.h" // For cli_options_t definition +#include "main.h" // For relay metadata constants /* * Default Configuration Event Template @@ -33,10 +34,16 @@ static const struct { {"max_connections", "100"}, // NIP-11 Relay Information (relay keys will be populated at runtime) - {"relay_description", "High-performance C Nostr relay with SQLite storage"}, - {"relay_contact", ""}, - {"relay_software", "https://git.laantungir.net/laantungir/c-relay.git"}, - {"relay_version", "v1.0.0"}, + {"relay_name", RELAY_NAME}, + {"relay_description", RELAY_DESCRIPTION}, + {"relay_contact", RELAY_CONTACT}, + {"relay_software", RELAY_SOFTWARE}, + {"relay_version", RELAY_VERSION}, + {"supported_nips", SUPPORTED_NIPS}, + {"language_tags", LANGUAGE_TAGS}, + {"relay_countries", RELAY_COUNTRIES}, + {"posting_policy", POSTING_POLICY}, + {"payments_url", PAYMENTS_URL}, // NIP-13 Proof of Work (pow_min_difficulty = 0 means PoW disabled) {"pow_min_difficulty", "0"}, diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..5020369 --- /dev/null +++ b/src/main.h @@ -0,0 +1,32 @@ +/* + * C-Relay Main Header - Version and Metadata Information + * + * This header contains version information and relay metadata that is + * automatically updated by the build system (build_and_push.sh). + * + * The build_and_push.sh script updates VERSION and related macros when + * creating new releases. + */ + +#ifndef MAIN_H +#define MAIN_H + +// Version information (auto-updated by build_and_push.sh) +#define VERSION "v0.4.2" +#define VERSION_MAJOR 0 +#define VERSION_MINOR 4 +#define VERSION_PATCH 2 + +// Relay metadata (authoritative source for NIP-11 information) +#define RELAY_NAME "C-Relay" +#define RELAY_DESCRIPTION "High-performance C Nostr relay with SQLite storage" +#define RELAY_CONTACT "" +#define RELAY_SOFTWARE "https://git.laantungir.net/laantungir/c-relay.git" +#define RELAY_VERSION VERSION // Use the same version as the build +#define SUPPORTED_NIPS "1,2,4,9,11,12,13,15,16,20,22,33,40,42" +#define LANGUAGE_TAGS "" +#define RELAY_COUNTRIES "" +#define POSTING_POLICY "" +#define PAYMENTS_URL "" + +#endif /* MAIN_H */ diff --git a/src/nip011.c b/src/nip011.c index 9fb6035..d47919a 100644 --- a/src/nip011.c +++ b/src/nip011.c @@ -34,76 +34,213 @@ extern unified_config_cache_t g_unified_cache; ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// +// Helper function to parse comma-separated string into cJSON array +cJSON* parse_comma_separated_array(const char* csv_string) { + log_info("parse_comma_separated_array called"); + if (!csv_string || strlen(csv_string) == 0) { + log_info("Empty or null csv_string, returning empty array"); + return cJSON_CreateArray(); + } + + log_info("Creating cJSON array"); + cJSON* array = cJSON_CreateArray(); + if (!array) { + log_info("Failed to create cJSON array"); + return NULL; + } + + log_info("Duplicating csv_string"); + char* csv_copy = strdup(csv_string); + if (!csv_copy) { + log_info("Failed to duplicate csv_string"); + cJSON_Delete(array); + return NULL; + } + + log_info("Starting token parsing"); + char* token = strtok(csv_copy, ","); + while (token) { + log_info("Processing token"); + // Trim whitespace + while (*token == ' ') token++; + char* end = token + strlen(token) - 1; + while (end > token && *end == ' ') *end-- = '\0'; + + if (strlen(token) > 0) { + log_info("Token has content, parsing"); + // Try to parse as number first (for supported_nips) + char* endptr; + long num = strtol(token, &endptr, 10); + if (*endptr == '\0') { + log_info("Token is number, adding to array"); + // It's a number + cJSON_AddItemToArray(array, cJSON_CreateNumber(num)); + } else { + log_info("Token is string, adding to array"); + // It's a string + cJSON_AddItemToArray(array, cJSON_CreateString(token)); + } + } else { + log_info("Token is empty, skipping"); + } + token = strtok(NULL, ","); + } + + log_info("Freeing csv_copy"); + free(csv_copy); + log_info("Returning parsed array"); + return array; +} + // Initialize relay information using configuration system void init_relay_info() { + log_info("Initializing relay information from configuration..."); + // Get all config values first (without holding mutex to avoid deadlock) + // Note: These may be dynamically allocated strings that need to be freed + log_info("Fetching relay configuration values..."); const char* relay_name = get_config_value("relay_name"); + log_info("relay_name fetched"); const char* relay_description = get_config_value("relay_description"); + log_info("relay_description fetched"); const char* relay_software = get_config_value("relay_software"); + log_info("relay_software fetched"); const char* relay_version = get_config_value("relay_version"); + log_info("relay_version fetched"); const char* relay_contact = get_config_value("relay_contact"); + log_info("relay_contact fetched"); const char* relay_pubkey = get_config_value("relay_pubkey"); - + log_info("relay_pubkey fetched"); + const char* supported_nips_csv = get_config_value("supported_nips"); + log_info("supported_nips fetched"); + const char* language_tags_csv = get_config_value("language_tags"); + log_info("language_tags fetched"); + const char* relay_countries_csv = get_config_value("relay_countries"); + log_info("relay_countries fetched"); + const char* posting_policy = get_config_value("posting_policy"); + log_info("posting_policy fetched"); + const char* payments_url = get_config_value("payments_url"); + log_info("payments_url fetched"); + // Get config values for limitations + log_info("Fetching limitation configuration values..."); int max_message_length = get_config_int("max_message_length", 16384); + log_info("max_message_length fetched"); int max_subscriptions_per_client = get_config_int("max_subscriptions_per_client", 20); + log_info("max_subscriptions_per_client fetched"); int max_limit = get_config_int("max_limit", 5000); + log_info("max_limit fetched"); int max_event_tags = get_config_int("max_event_tags", 100); + log_info("max_event_tags fetched"); int max_content_length = get_config_int("max_content_length", 8196); + log_info("max_content_length fetched"); int default_limit = get_config_int("default_limit", 500); + log_info("default_limit fetched"); int admin_enabled = get_config_bool("admin_enabled", 0); - + log_info("admin_enabled fetched"); + pthread_mutex_lock(&g_unified_cache.cache_lock); - + // Update relay information fields + log_info("Storing string values in cache..."); if (relay_name) { + log_info("Storing relay_name"); strncpy(g_unified_cache.relay_info.name, relay_name, sizeof(g_unified_cache.relay_info.name) - 1); + free((char*)relay_name); // Free dynamically allocated string + log_info("relay_name stored and freed"); } else { + log_info("Using default relay_name"); strncpy(g_unified_cache.relay_info.name, "C Nostr Relay", sizeof(g_unified_cache.relay_info.name) - 1); } - + if (relay_description) { + log_info("Storing relay_description"); strncpy(g_unified_cache.relay_info.description, relay_description, sizeof(g_unified_cache.relay_info.description) - 1); + free((char*)relay_description); // Free dynamically allocated string + log_info("relay_description stored and freed"); } else { + log_info("Using default relay_description"); strncpy(g_unified_cache.relay_info.description, "A high-performance Nostr relay implemented in C with SQLite storage", sizeof(g_unified_cache.relay_info.description) - 1); } - + if (relay_software) { + log_info("Storing relay_software"); strncpy(g_unified_cache.relay_info.software, relay_software, sizeof(g_unified_cache.relay_info.software) - 1); + free((char*)relay_software); // Free dynamically allocated string + log_info("relay_software stored and freed"); } else { + log_info("Using default relay_software"); strncpy(g_unified_cache.relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git", sizeof(g_unified_cache.relay_info.software) - 1); } - + if (relay_version) { + log_info("Storing relay_version"); strncpy(g_unified_cache.relay_info.version, relay_version, sizeof(g_unified_cache.relay_info.version) - 1); + free((char*)relay_version); // Free dynamically allocated string + log_info("relay_version stored and freed"); } else { + log_info("Using default relay_version"); strncpy(g_unified_cache.relay_info.version, "0.2.0", sizeof(g_unified_cache.relay_info.version) - 1); } - + if (relay_contact) { + log_info("Storing relay_contact"); strncpy(g_unified_cache.relay_info.contact, relay_contact, sizeof(g_unified_cache.relay_info.contact) - 1); + free((char*)relay_contact); // Free dynamically allocated string + log_info("relay_contact stored and freed"); } - + if (relay_pubkey) { + log_info("Storing relay_pubkey"); strncpy(g_unified_cache.relay_info.pubkey, relay_pubkey, sizeof(g_unified_cache.relay_info.pubkey) - 1); + free((char*)relay_pubkey); // Free dynamically allocated string + log_info("relay_pubkey stored and freed"); + } + + if (posting_policy) { + log_info("Storing posting_policy"); + strncpy(g_unified_cache.relay_info.posting_policy, posting_policy, sizeof(g_unified_cache.relay_info.posting_policy) - 1); + free((char*)posting_policy); // Free dynamically allocated string + log_info("posting_policy stored and freed"); + } + + if (payments_url) { + log_info("Storing payments_url"); + strncpy(g_unified_cache.relay_info.payments_url, payments_url, sizeof(g_unified_cache.relay_info.payments_url) - 1); + free((char*)payments_url); // Free dynamically allocated string + log_info("payments_url stored and freed"); } - // Initialize supported NIPs array - g_unified_cache.relay_info.supported_nips = cJSON_CreateArray(); - if (g_unified_cache.relay_info.supported_nips) { - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(1)); // NIP-01: Basic protocol - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(9)); // NIP-09: Event deletion - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(11)); // NIP-11: Relay information - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(13)); // NIP-13: Proof of Work - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(15)); // NIP-15: EOSE - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(20)); // NIP-20: Command results - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(40)); // NIP-40: Expiration Timestamp - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(42)); // NIP-42: Authentication + // Initialize supported NIPs array from config + log_info("Initializing supported_nips array"); + if (supported_nips_csv) { + log_info("Parsing supported_nips from config"); + g_unified_cache.relay_info.supported_nips = parse_comma_separated_array(supported_nips_csv); + log_info("supported_nips parsed successfully"); + free((char*)supported_nips_csv); // Free dynamically allocated string + log_info("supported_nips_csv freed"); + } else { + log_info("Using default supported_nips"); + // Fallback to default supported NIPs + g_unified_cache.relay_info.supported_nips = cJSON_CreateArray(); + if (g_unified_cache.relay_info.supported_nips) { + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(1)); // NIP-01: Basic protocol + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(9)); // NIP-09: Event deletion + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(11)); // NIP-11: Relay information + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(13)); // NIP-13: Proof of Work + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(15)); // NIP-15: EOSE + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(20)); // NIP-20: Command results + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(40)); // NIP-40: Expiration Timestamp + cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(42)); // NIP-42: Authentication + } + log_info("Default supported_nips created"); } // Initialize server limitations using configuration + log_info("Initializing server limitations"); g_unified_cache.relay_info.limitation = cJSON_CreateObject(); if (g_unified_cache.relay_info.limitation) { + log_info("Adding limitation fields"); cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_message_length", max_message_length); cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_subscriptions", max_subscriptions_per_client); cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_limit", max_limit); @@ -117,31 +254,60 @@ void init_relay_info() { cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_lower_limit", 0); cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_upper_limit", 2147483647); cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "default_limit", default_limit); + log_info("Limitation fields added"); + } else { + log_info("Failed to create limitation object"); } // Initialize empty retention policies (can be configured later) + log_info("Initializing retention policies"); g_unified_cache.relay_info.retention = cJSON_CreateArray(); - - // Initialize language tags - set to global for now - g_unified_cache.relay_info.language_tags = cJSON_CreateArray(); - if (g_unified_cache.relay_info.language_tags) { - cJSON_AddItemToArray(g_unified_cache.relay_info.language_tags, cJSON_CreateString("*")); + + // Initialize language tags from config + log_info("Initializing language_tags"); + if (language_tags_csv) { + log_info("Parsing language_tags from config"); + g_unified_cache.relay_info.language_tags = parse_comma_separated_array(language_tags_csv); + log_info("language_tags parsed successfully"); + free((char*)language_tags_csv); // Free dynamically allocated string + log_info("language_tags_csv freed"); + } else { + log_info("Using default language_tags"); + // Fallback to global + g_unified_cache.relay_info.language_tags = cJSON_CreateArray(); + if (g_unified_cache.relay_info.language_tags) { + cJSON_AddItemToArray(g_unified_cache.relay_info.language_tags, cJSON_CreateString("*")); + } } - - // Initialize relay countries - set to global for now - g_unified_cache.relay_info.relay_countries = cJSON_CreateArray(); - if (g_unified_cache.relay_info.relay_countries) { - cJSON_AddItemToArray(g_unified_cache.relay_info.relay_countries, cJSON_CreateString("*")); + + // Initialize relay countries from config + log_info("Initializing relay_countries"); + if (relay_countries_csv) { + log_info("Parsing relay_countries from config"); + g_unified_cache.relay_info.relay_countries = parse_comma_separated_array(relay_countries_csv); + log_info("relay_countries parsed successfully"); + free((char*)relay_countries_csv); // Free dynamically allocated string + log_info("relay_countries_csv freed"); + } else { + log_info("Using default relay_countries"); + // Fallback to global + g_unified_cache.relay_info.relay_countries = cJSON_CreateArray(); + if (g_unified_cache.relay_info.relay_countries) { + cJSON_AddItemToArray(g_unified_cache.relay_info.relay_countries, cJSON_CreateString("*")); + } } - + // Initialize content tags as empty array + log_info("Initializing tags"); g_unified_cache.relay_info.tags = cJSON_CreateArray(); - + // Initialize fees as empty object (no payment required by default) + log_info("Initializing fees"); g_unified_cache.relay_info.fees = cJSON_CreateObject(); - + + log_info("Unlocking cache mutex"); pthread_mutex_unlock(&g_unified_cache.cache_lock); - + log_success("Relay information initialized with default values"); } diff --git a/src/nip013.c b/src/nip013.c index a8b91e0..832df38 100644 --- a/src/nip013.c +++ b/src/nip013.c @@ -60,6 +60,7 @@ void init_pow_config() { g_unified_cache.pow_config.enabled = 0; log_info("PoW validation disabled via configuration"); } + free((char*)pow_mode); // Free dynamically allocated string } else { // Default to basic mode g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC; diff --git a/src/request_validator.c b/src/request_validator.c index 68803ab..21177d6 100644 --- a/src/request_validator.c +++ b/src/request_validator.c @@ -211,13 +211,15 @@ int ginxsom_request_validator_init(const char *db_path, const char *app_name) { // Initialize NIP-42 challenge manager using unified config memset(&g_challenge_manager, 0, sizeof(g_challenge_manager)); - + const char* nip42_timeout = get_config_value("nip42_challenge_timeout"); g_challenge_manager.timeout_seconds = nip42_timeout ? atoi(nip42_timeout) : 600; - + if (nip42_timeout) free((char*)nip42_timeout); + const char* nip42_tolerance = get_config_value("nip42_time_tolerance"); g_challenge_manager.time_tolerance_seconds = nip42_tolerance ? atoi(nip42_tolerance) : 300; - + if (nip42_tolerance) free((char*)nip42_tolerance); + g_challenge_manager.last_cleanup = time(NULL); g_validator_initialized = 1; @@ -232,13 +234,20 @@ int ginxsom_request_validator_init(const char *db_path, const char *app_name) { int nostr_auth_rules_enabled(void) { // Use unified cache from config.c const char* auth_enabled = get_config_value("auth_enabled"); + int result = 0; if (auth_enabled && strcmp(auth_enabled, "true") == 0) { - return 1; + result = 1; } - + if (auth_enabled) free((char*)auth_enabled); + // Also check legacy key const char* auth_rules_enabled = get_config_value("auth_rules_enabled"); - return (auth_rules_enabled && strcmp(auth_rules_enabled, "true") == 0) ? 1 : 0; + if (auth_rules_enabled && strcmp(auth_rules_enabled, "true") == 0) { + result = 1; + } + if (auth_rules_enabled) free((char*)auth_rules_enabled); + + return result; } /////////////////////////////////////////////////////////////////////////////////////// @@ -344,9 +353,11 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length) const char* nip42_enabled = get_config_value("nip42_auth_enabled"); if (nip42_enabled && strcmp(nip42_enabled, "false") == 0) { validator_debug_log("VALIDATOR_DEBUG: STEP 8 FAILED - NIP-42 is disabled\n"); + free((char*)nip42_enabled); cJSON_Delete(event); return NOSTR_ERROR_NIP42_DISABLED; } + if (nip42_enabled) free((char*)nip42_enabled); // TODO: Implement full NIP-42 challenge validation // For now, accept all valid NIP-42 events