From 670329700c59f6fb321684297b593d9c4e151177 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 13 Oct 2025 19:06:27 -0400 Subject: [PATCH] v0.7.14 - Remove unified config cache system and fix first-time startup - All config values now queried directly from database, eliminating cache inconsistency bugs. Fixed startup sequence to use output parameters for pubkey passing. --- .../event_based_config_implementation_plan.md | 358 ------- docs/startup_config_analysis.md | 128 --- relay.pid | 2 +- src/config.c | 870 +++--------------- src/config.h | 77 +- src/default_config_event.h | 2 + src/main.c | 49 +- src/nip011.c | 556 ++++------- src/nip013.c | 75 +- src/request_validator.c | 13 +- src/sql_schema.h | 28 - src/subscriptions.c | 10 +- src/websockets.c | 8 +- 13 files changed, 377 insertions(+), 1799 deletions(-) delete mode 100644 docs/event_based_config_implementation_plan.md delete mode 100644 docs/startup_config_analysis.md diff --git a/docs/event_based_config_implementation_plan.md b/docs/event_based_config_implementation_plan.md deleted file mode 100644 index d897283..0000000 --- a/docs/event_based_config_implementation_plan.md +++ /dev/null @@ -1,358 +0,0 @@ -# Event-Based Configuration System Implementation Plan - -## Overview - -This document provides a detailed implementation plan for transitioning the C Nostr Relay from command line arguments and file-based configuration to a pure event-based configuration system using kind 33334 Nostr events stored directly in the database. - -## Implementation Phases - -### Phase 0: File Structure Preparation ✅ COMPLETED - -#### 0.1 Backup and Prepare Files ✅ COMPLETED -**Actions:** -1. ✅ Rename `src/config.c` to `src/config.c.old` - DONE -2. ✅ Rename `src/config.h` to `src/config.h.old` - DONE -3. ✅ Create new empty `src/config.c` and `src/config.h` - DONE -4. ✅ Create new `src/default_config_event.h` - DONE - -### Phase 1: Database Schema and Core Infrastructure ✅ COMPLETED - -#### 1.1 Update Database Naming System ✅ COMPLETED -**File:** `src/main.c`, new `src/config.c`, new `src/config.h` - -```c -// New functions implemented: ✅ -char* get_database_name_from_relay_pubkey(const char* relay_pubkey); -int create_database_with_relay_pubkey(const char* relay_pubkey); -``` - -**Changes Completed:** ✅ -- ✅ Create completely new `src/config.c` and `src/config.h` files -- ✅ Rename old files to `src/config.c.old` and `src/config.h.old` -- ✅ Modify `init_database()` to use relay pubkey for database naming -- ✅ Use `nostr_core_lib` functions for all keypair generation -- ✅ Database path: `./.nrdb` -- ✅ Remove all database path command line argument handling - -#### 1.2 Configuration Event Storage ✅ COMPLETED -**File:** new `src/config.c`, new `src/default_config_event.h` - -```c -// Configuration functions implemented: ✅ -int store_config_event_in_database(const cJSON* event); -cJSON* load_config_event_from_database(const char* relay_pubkey); -``` - -**Changes Completed:** ✅ -- ✅ Create new `src/default_config_event.h` for default configuration values -- ✅ Add functions to store/retrieve kind 33334 events from events table -- ✅ Use `nostr_core_lib` functions for all event validation -- ✅ Clean separation: default config values isolated in header file -- ✅ Remove existing config table dependencies - -### Phase 2: Event Processing Integration ✅ COMPLETED - -#### 2.1 Real-time Configuration Processing ✅ COMPLETED -**File:** `src/main.c` (event processing functions) - -**Integration Points:** ✅ IMPLEMENTED -```c -// In existing event processing loop: ✅ IMPLEMENTED -// Added kind 33334 event detection in main event loop -if (kind_num == 33334) { - if (handle_configuration_event(event, error_message, sizeof(error_message)) == 0) { - // Configuration event processed successfully - } -} - -// Configuration event processing implemented: ✅ -int process_configuration_event(const cJSON* event); -int handle_configuration_event(cJSON* event, char* error_message, size_t error_size); -``` - -#### 2.2 Configuration Application System ⚠️ PARTIALLY COMPLETED -**File:** `src/config.c` - -**Status:** Configuration access functions implemented, field handlers need completion -```c -// Configuration access implemented: ✅ -const char* get_config_value(const char* key); -int get_config_int(const char* key, int default_value); -int get_config_bool(const char* key, int default_value); - -// Field handlers need implementation: ⏳ IN PROGRESS -// Need to implement specific apply functions for runtime changes -``` - -### Phase 3: First-Time Startup System ✅ COMPLETED - -#### 3.1 Key Generation and Initial Setup ✅ COMPLETED -**File:** new `src/config.c`, `src/default_config_event.h` - -**Status:** ✅ FULLY IMPLEMENTED with secure /dev/urandom + nostr_core_lib validation - -```c -int first_time_startup_sequence() { - // 1. Generate admin keypair using nostr_core_lib - unsigned char admin_privkey_bytes[32]; - char admin_privkey[65], admin_pubkey[65]; - - if (nostr_generate_private_key(admin_privkey_bytes) != 0) { - return -1; - } - nostr_bytes_to_hex(admin_privkey_bytes, 32, admin_privkey); - - unsigned char admin_pubkey_bytes[32]; - if (nostr_ec_public_key_from_private_key(admin_privkey_bytes, admin_pubkey_bytes) != 0) { - return -1; - } - nostr_bytes_to_hex(admin_pubkey_bytes, 32, admin_pubkey); - - // 2. Generate relay keypair using nostr_core_lib - unsigned char relay_privkey_bytes[32]; - char relay_privkey[65], relay_pubkey[65]; - - if (nostr_generate_private_key(relay_privkey_bytes) != 0) { - return -1; - } - nostr_bytes_to_hex(relay_privkey_bytes, 32, relay_privkey); - - unsigned char relay_pubkey_bytes[32]; - if (nostr_ec_public_key_from_private_key(relay_privkey_bytes, relay_pubkey_bytes) != 0) { - return -1; - } - nostr_bytes_to_hex(relay_pubkey_bytes, 32, relay_pubkey); - - // 3. Create database with relay pubkey name - if (create_database_with_relay_pubkey(relay_pubkey) != 0) { - return -1; - } - - // 4. Create initial configuration event using defaults from header - cJSON* config_event = create_default_config_event(admin_privkey_bytes, relay_privkey, relay_pubkey); - - // 5. Store configuration event in database - store_config_event_in_database(config_event); - - // 6. Print admin private key for user to save - printf("=== SAVE THIS ADMIN PRIVATE KEY ===\n"); - printf("Admin Private Key: %s\n", admin_privkey); - printf("===================================\n"); - - return 0; -} -``` - -#### 3.2 Database Detection Logic ✅ COMPLETED -**File:** `src/main.c` - -**Status:** ✅ FULLY IMPLEMENTED -```c -// Implemented functions: ✅ -char** find_existing_nrdb_files(void); -char* extract_pubkey_from_filename(const char* filename); -int is_first_time_startup(void); -int first_time_startup_sequence(void); -int startup_existing_relay(const char* relay_pubkey); -``` - -### Phase 4: Legacy System Removal ✅ PARTIALLY COMPLETED - -#### 4.1 Remove Command Line Arguments ✅ COMPLETED -**File:** `src/main.c` - -**Status:** ✅ COMPLETED -- ✅ All argument parsing logic removed except --help and --version -- ✅ `--port`, `--config-dir`, `--config-file`, `--database-path` handling removed -- ✅ Environment variable override systems removed -- ✅ Clean help and version functions implemented - -#### 4.2 Remove Configuration File System ✅ COMPLETED -**File:** `src/config.c` - -**Status:** ✅ COMPLETED - New file created from scratch -- ✅ All legacy file-based configuration functions removed -- ✅ XDG configuration directory logic removed -- ✅ Pure event-based system implemented - -#### 4.3 Remove Legacy Database Tables ⏳ PENDING -**File:** `src/sql_schema.h` - -**Status:** ⏳ NEEDS COMPLETION -```sql --- Still need to remove these tables: -DROP TABLE IF EXISTS config; -DROP TABLE IF EXISTS config_history; -DROP TABLE IF EXISTS config_file_cache; -DROP VIEW IF EXISTS active_config; -``` - -### Phase 5: Configuration Management - -#### 5.1 Configuration Field Mapping -**File:** `src/config.c` - -```c -// Map configuration tags to current system -static const config_field_handler_t config_handlers[] = { - {"auth_enabled", 0, apply_auth_enabled}, - {"relay_port", 1, apply_relay_port}, // requires restart - {"max_connections", 0, apply_max_connections}, - {"relay_description", 0, apply_relay_description}, - {"relay_contact", 0, apply_relay_contact}, - {"relay_pubkey", 1, apply_relay_pubkey}, // requires restart - {"relay_privkey", 1, apply_relay_privkey}, // requires restart - {"pow_min_difficulty", 0, apply_pow_difficulty}, - {"nip40_expiration_enabled", 0, apply_expiration_enabled}, - {"max_subscriptions_per_client", 0, apply_max_subscriptions}, - {"max_event_tags", 0, apply_max_event_tags}, - {"max_content_length", 0, apply_max_content_length}, - {"default_limit", 0, apply_default_limit}, - {"max_limit", 0, apply_max_limit}, - // ... etc -}; -``` - -#### 5.2 Startup Configuration Loading -**File:** `src/main.c` - -```c -int startup_existing_relay(const char* relay_pubkey) { - // 1. Open database - if (init_database_with_pubkey(relay_pubkey) != 0) { - return -1; - } - - // 2. Load configuration event from database - cJSON* config_event = load_config_event_from_database(relay_pubkey); - if (!config_event) { - log_error("No configuration event found in database"); - return -1; - } - - // 3. Apply all configuration from event - if (apply_configuration_from_event(config_event) != 0) { - return -1; - } - - // 4. Continue with normal startup - return start_relay_services(); -} -``` - -## Implementation Order - PROGRESS STATUS - -### Step 1: Core Infrastructure ✅ COMPLETED -1. ✅ Implement database naming with relay pubkey -2. ✅ Add key generation functions using `nostr_core_lib` -3. ✅ Create configuration event storage/retrieval functions -4. ✅ Test basic event creation and storage - -### Step 2: Event Processing Integration ✅ MOSTLY COMPLETED -1. ✅ Add kind 33334 event detection to event processing loop -2. ✅ Implement configuration event validation -3. ⚠️ Create configuration application handlers (basic access implemented, runtime handlers pending) -4. ⏳ Test real-time configuration updates (infrastructure ready) - -### Step 3: First-Time Startup ✅ COMPLETED -1. ✅ Implement first-time startup detection -2. ✅ Add automatic key generation and database creation -3. ✅ Create default configuration event generation -4. ✅ Test complete first-time startup flow - -### Step 4: Legacy Removal ⚠️ MOSTLY COMPLETED -1. ✅ Remove command line argument parsing -2. ✅ Remove configuration file system -3. ⏳ Remove legacy database tables (pending) -4. ✅ Update all references to use event-based config - -### Step 5: Testing and Validation ⚠️ PARTIALLY COMPLETED -1. ✅ Test complete startup flow (first time and existing) -2. ⏳ Test configuration updates via events (infrastructure ready) -3. ⚠️ Test error handling and recovery (basic error handling implemented) -4. ⏳ Performance testing and optimization (pending) - -## Migration Strategy - -### For Existing Installations -Since the new system uses a completely different approach: - -1. **No Automatic Migration**: The new system starts fresh -2. **Manual Migration**: Users can manually copy configuration values -3. **Documentation**: Provide clear migration instructions -4. **Coexistence**: Old and new systems use different database names - -### Migration Steps for Users -1. Stop existing relay -2. Note current configuration values -3. Start new relay (generates keys and new database) -4. Create kind 33334 event with desired configuration using admin private key -5. Send event to relay to update configuration - -## Testing Requirements - -### Unit Tests -- Key generation functions -- Configuration event creation and validation -- Database naming logic -- Configuration application handlers - -### Integration Tests -- Complete first-time startup flow -- Configuration update via events -- Error handling scenarios -- Database operations - -### Performance Tests -- Startup time comparison -- Configuration update response time -- Memory usage analysis - -## Security Considerations - -1. **Admin Private Key**: Never stored, only printed once -2. **Event Validation**: All configuration events must be signed by admin -3. **Database Security**: Relay database contains relay private key -4. **Key Generation**: Use `nostr_core_lib` for cryptographically secure generation - -## Files to Modify - -### Major Changes -- `src/main.c` - Startup logic, event processing, argument removal -- `src/config.c` - Complete rewrite for event-based configuration -- `src/config.h` - Update function signatures and structures -- `src/sql_schema.h` - Remove config tables - -### Minor Changes -- `Makefile` - Remove any config file generation -- `systemd/` - Update service files if needed -- Documentation updates - -## Backwards Compatibility - -**Breaking Changes:** -- Command line arguments removed (except --help, --version) -- Configuration files no longer used -- Database naming scheme changed -- Configuration table removed - -**Migration Required:** This is a breaking change that requires manual migration for existing installations. - -## Success Criteria - CURRENT STATUS - -1. ✅ **Zero Command Line Arguments**: Relay starts with just `./c-relay` -2. ✅ **Automatic First-Time Setup**: Generates keys and database automatically -3. ⚠️ **Real-Time Configuration**: Infrastructure ready, handlers need completion -4. ✅ **Single Database File**: All configuration and data in one `.nrdb` file -5. ⚠️ **Admin Control**: Event processing implemented, signature validation ready -6. ⚠️ **Clean Codebase**: Most legacy code removed, database tables cleanup pending - -## Risk Mitigation - -1. **Backup Strategy**: Document manual backup procedures for relay database -2. **Key Loss Recovery**: Document recovery procedures if admin key is lost -3. **Testing Coverage**: Comprehensive test suite before deployment -4. **Rollback Plan**: Keep old version available during transition period -5. **Documentation**: Comprehensive user and developer documentation - -This implementation plan provides a clear path from the current system to the new event-based configuration architecture while maintaining security and reliability. \ No newline at end of file diff --git a/docs/startup_config_analysis.md b/docs/startup_config_analysis.md deleted file mode 100644 index fab9205..0000000 --- a/docs/startup_config_analysis.md +++ /dev/null @@ -1,128 +0,0 @@ -# Startup Configuration Design Analysis - -## Review of startup_config_design.md - -### Key Design Principles Identified - -1. **Zero Command Line Arguments**: Complete elimination of CLI arguments for true "quick start" -2. **Event-Based Configuration**: Configuration stored as Nostr event (kind 33334) in events table -3. **Self-Contained Database**: Database named after relay pubkey (`.nrdb`) -4. **First-Time Setup**: Automatic key generation and initial configuration creation -5. **Configuration Consistency**: Always read from event, never from hardcoded defaults - -### Implementation Gaps and Specifications Needed - -#### 1. Key Generation Process -**Specification:** -``` -First Startup Key Generation: -1. Generate all keys on first startup (admin private/public, relay private/public) -2. Use nostr_core_lib for key generation entropy -3. Keys are encoded in hex format -4. Print admin private key to stdout for user to save (never stored) -5. Store admin public key, relay private key, and relay public key in configuration event -6. Admin can later change the 33334 event to alter stored keys -``` - -#### 2. Database Naming and Location -**Specification:** -``` -Database Naming: -1. Database is named using relay pubkey: ./.nrdb -2. Database path structure: ./.nrdb -3. If database creation fails, program quits (can't run without database) -4. c_nostr_relay.db should never exist in new system -``` - -#### 3. Configuration Event Structure (Kind 33334) -**Specification:** -``` -Event Structure: -- Kind: 33334 (parameterized replaceable event) -- Event validation: Use nostr_core_lib to validate event -- Event content field: "C Nostr Relay Configuration" (descriptive text) -- Configuration update mechanism: TBD -- Complete tag structure provided in configuration section below -``` - - - -#### 4. Configuration Change Monitoring -**Configuration Monitoring System:** -``` -Every event that is received is checked to see if it is a kind 33334 event from the admin pubkey. -If so, it is processed as a configuration update. -``` - -#### 5. Error Handling and Recovery -**Specification:** -``` -Error Recovery Priority: -1. Try to load latest valid config event -2. Generate new default configuration event if none exists -3. Exit with error if all recovery attempts fail - -Note: There is only ever one configuration event (parameterized replaceable event), -so no fallback to previous versions. -``` - -### Design Clarifications - -**Key Management:** -- Admin private key is never stored, only printed once at first startup -- Single admin system (no multi-admin support) -- No key rotation support - -**Configuration Management:** -- No configuration versioning/timestamping -- No automatic backup of configuration events -- Configuration events are not broadcastable to other relays -- Future: Auth system to restrict admin access to configuration events - ---- - -## Complete Current Configuration Structure - -Based on analysis of [`src/config.c`](src/config.c:753-795), here is the complete current configuration structure that will be converted to event tags: - -### Complete Event Structure Example -```json -{ - "kind": 33334, - "created_at": 1725661483, - "tags": [ - ["d", ""], - ["auth_enabled", "false"], - ["relay_port", "8888"], - ["max_connections", "100"], - - ["relay_description", "High-performance C Nostr relay with SQLite storage"], - ["relay_contact", ""], - ["relay_pubkey", ""], - ["relay_privkey", ""], - ["relay_software", "https://git.laantungir.net/laantungir/c-relay.git"], - ["relay_version", "v1.0.0"], - - ["pow_min_difficulty", "0"], - ["pow_mode", "basic"], - ["nip40_expiration_enabled", "true"], - ["nip40_expiration_strict", "true"], - ["nip40_expiration_filter", "true"], - ["nip40_expiration_grace_period", "300"], - ["max_subscriptions_per_client", "25"], - ["max_total_subscriptions", "5000"], - ["max_filters_per_subscription", "10"], - ["max_event_tags", "100"], - ["max_content_length", "8196"], - ["max_message_length", "16384"], - ["default_limit", "500"], - ["max_limit", "5000"] - ], - "content": "C Nostr Relay Configuration", - "pubkey": "", - "id": "", - "sig": "" -} -``` - -**Note:** The `admin_pubkey` tag is omitted as it's redundant with the event's `pubkey` field. diff --git a/relay.pid b/relay.pid index 7b1163b..e80f96a 100644 --- a/relay.pid +++ b/relay.pid @@ -1 +1 @@ -2244870 +2614899 diff --git a/src/config.c b/src/config.c index d198e91..8315136 100644 --- a/src/config.c +++ b/src/config.c @@ -21,12 +21,7 @@ extern sqlite3* g_db; // External shutdown flag (from main.c) extern volatile sig_atomic_t g_shutdown_flag; -// Global unified configuration cache instance -unified_config_cache_t g_unified_cache = { - .cache_lock = PTHREAD_MUTEX_INITIALIZER, - .cache_valid = 0, - .cache_expires = 0 -}; +// Database path for event-based config char g_database_path[512] = {0}; // ================================ @@ -65,7 +60,6 @@ typedef enum { int populate_default_config_values(void); int populate_all_config_values_atomic(const char* admin_pubkey, const char* relay_pubkey); int process_admin_config_event(cJSON* event, char* error_message, size_t error_size); -void invalidate_config_cache(void); // Forward declaration for relay info initialization void init_relay_info(void); @@ -96,446 +90,6 @@ static cJSON* g_pending_config_event = NULL; // Temporary storage for relay private key during first-time setup static char g_temp_relay_privkey[65] = {0}; -// ================================ -// UNIFIED CACHE MANAGEMENT FUNCTIONS -// ================================ - -// Get cache timeout from environment variable or default (similar to request_validator) -static int get_cache_timeout(void) { - char *no_cache = getenv("GINX_NO_CACHE"); - char *cache_timeout = getenv("GINX_CACHE_TIMEOUT"); - - if (no_cache && strcmp(no_cache, "1") == 0) { - return 0; // No caching - } - - if (cache_timeout) { - int timeout = atoi(cache_timeout); - return (timeout >= 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); - g_unified_cache.cache_valid = 0; - g_unified_cache.cache_expires = 0; - pthread_mutex_unlock(&g_unified_cache.cache_lock); -} - -// Update specific cache value without full refresh -int update_cache_value(const char* key, const char* value) { - if (!key || !value) { - return -1; - } - - pthread_mutex_lock(&g_unified_cache.cache_lock); - - // Update specific cache fields - if (strcmp(key, "admin_pubkey") == 0) { - strncpy(g_unified_cache.admin_pubkey, value, sizeof(g_unified_cache.admin_pubkey) - 1); - g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0'; - } else if (strcmp(key, "relay_pubkey") == 0) { - strncpy(g_unified_cache.relay_pubkey, value, sizeof(g_unified_cache.relay_pubkey) - 1); - g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0'; - } else if (strcmp(key, "auth_required") == 0) { - g_unified_cache.auth_required = (strcmp(value, "true") == 0) ? 1 : 0; - } else if (strcmp(key, "admin_enabled") == 0) { - g_unified_cache.admin_enabled = (strcmp(value, "true") == 0) ? 1 : 0; - } else if (strcmp(key, "max_file_size") == 0) { - g_unified_cache.max_file_size = atol(value); - } else if (strcmp(key, "nip42_mode") == 0) { - if (strcmp(value, "disabled") == 0) { - g_unified_cache.nip42_mode = 0; - } else if (strcmp(value, "required") == 0) { - g_unified_cache.nip42_mode = 2; - } else { - g_unified_cache.nip42_mode = 1; // Optional/enabled - } - } else if (strcmp(key, "nip42_challenge_timeout") == 0) { - g_unified_cache.nip42_challenge_timeout = atoi(value); - } else if (strcmp(key, "nip42_time_tolerance") == 0) { - g_unified_cache.nip42_time_tolerance = atoi(value); - } else if (strcmp(key, "nip70_protected_events_enabled") == 0) { - g_unified_cache.nip70_protected_events_enabled = (strcmp(value, "true") == 0) ? 1 : 0; - } else { - // For NIP-11 relay info fields, update the cache buffers - if (strcmp(key, "relay_name") == 0) { - 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'; - } else if (strcmp(key, "relay_description") == 0) { - 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'; - } else if (strcmp(key, "relay_contact") == 0) { - 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'; - } else if (strcmp(key, "relay_software") == 0) { - 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'; - } else if (strcmp(key, "relay_version") == 0) { - 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'; - } else if (strcmp(key, "supported_nips") == 0) { - 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'; - } else if (strcmp(key, "language_tags") == 0) { - 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'; - } else if (strcmp(key, "relay_countries") == 0) { - 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'; - } else if (strcmp(key, "posting_policy") == 0) { - 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'; - } else if (strcmp(key, "payments_url") == 0) { - 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'; - } - } - - // Reset cache expiration to extend validity - int cache_timeout = get_cache_timeout(); - g_unified_cache.cache_expires = time(NULL) + cache_timeout; - - pthread_mutex_unlock(&g_unified_cache.cache_lock); - - printf(" Key: %s\n", key); - return 0; -} - -// Refresh unified cache from database -static int refresh_unified_cache_from_table(void) { - DEBUG_TRACE("Entering refresh_unified_cache_from_table()"); - - if (!g_db) { - DEBUG_ERROR("Database not available for cache refresh"); - DEBUG_TRACE("Exiting refresh_unified_cache_from_table() - no database"); - return -1; - } - - DEBUG_LOG("Starting unified cache refresh from database"); - - // Log config table row count at start of refresh_unified_cache_from_table - sqlite3_stmt* count_stmt; - const char* count_sql = "SELECT COUNT(*) FROM config"; - int config_count = 0; - if (sqlite3_prepare_v2(g_db, count_sql, -1, &count_stmt, NULL) == SQLITE_OK) { - if (sqlite3_step(count_stmt) == SQLITE_ROW) { - config_count = sqlite3_column_int(count_stmt, 0); - } - sqlite3_finalize(count_stmt); - } - - // Lock the cache for update (don't memset entire cache to avoid wiping relay_info) - pthread_mutex_lock(&g_unified_cache.cache_lock); - - // Load critical config values from table - const char* admin_pubkey = get_config_value_from_table("admin_pubkey"); - DEBUG_TRACE("get_config_value_from_table('admin_pubkey') returned: %s", - admin_pubkey ? admin_pubkey : "NULL"); - DEBUG_LOG("Loading admin_pubkey from config table: %s", admin_pubkey ? admin_pubkey : "NULL"); - if (admin_pubkey) { - DEBUG_LOG("Setting admin_pubkey in cache: %s", admin_pubkey); - strncpy(g_unified_cache.admin_pubkey, admin_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1); - g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0'; - DEBUG_TRACE("Set admin_pubkey in cache: %s", g_unified_cache.admin_pubkey); - free((char*)admin_pubkey); - } else { - DEBUG_LOG("No admin_pubkey found in config table"); - } - - const char* relay_pubkey = get_config_value_from_table("relay_pubkey"); - DEBUG_TRACE("get_config_value_from_table('relay_pubkey') returned: %s", - relay_pubkey ? relay_pubkey : "NULL"); - DEBUG_LOG("Loading relay_pubkey from config table: %s", relay_pubkey ? relay_pubkey : "NULL"); - if (relay_pubkey) { - DEBUG_LOG("Setting relay_pubkey in cache: %s", relay_pubkey); - 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'; - DEBUG_TRACE("Set relay_pubkey in cache: %s", g_unified_cache.relay_pubkey); - free((char*)relay_pubkey); - } else { - DEBUG_LOG("No relay_pubkey found in config table"); - } - - // Load auth-related config - const char* auth_required = get_config_value_from_table("auth_required"); - g_unified_cache.auth_required = (auth_required && strcmp(auth_required, "true") == 0) ? 1 : 0; - DEBUG_TRACE("Loaded auth_required from table: %s (cache value: %d)", - auth_required ? auth_required : "NULL", g_unified_cache.auth_required); - if (auth_required) free((char*)auth_required); - - const char* admin_enabled = get_config_value_from_table("admin_enabled"); - g_unified_cache.admin_enabled = (admin_enabled && strcmp(admin_enabled, "true") == 0) ? 1 : 0; - DEBUG_TRACE("Loaded admin_enabled from table: %s (cache value: %d)", - admin_enabled ? admin_enabled : "NULL", g_unified_cache.admin_enabled); - if (admin_enabled) free((char*)admin_enabled); - - const char* max_file_size = get_config_value_from_table("max_file_size"); - g_unified_cache.max_file_size = max_file_size ? atol(max_file_size) : 104857600; // 100MB default - DEBUG_TRACE("Loaded max_file_size from table: %s (cache value: %ld)", - max_file_size ? max_file_size : "NULL", g_unified_cache.max_file_size); - if (max_file_size) free((char*)max_file_size); - - const char* nip42_mode = get_config_value_from_table("nip42_mode"); - if (nip42_mode) { - if (strcmp(nip42_mode, "disabled") == 0) { - g_unified_cache.nip42_mode = 0; - } else if (strcmp(nip42_mode, "required") == 0) { - g_unified_cache.nip42_mode = 2; - } else { - g_unified_cache.nip42_mode = 1; // Optional/enabled - } - DEBUG_TRACE("Loaded nip42_mode from table: %s (cache value: %d)", - nip42_mode, g_unified_cache.nip42_mode); - free((char*)nip42_mode); - } else { - g_unified_cache.nip42_mode = 1; // Default to optional/enabled - } - - const char* challenge_timeout = get_config_value_from_table("nip42_challenge_timeout"); - g_unified_cache.nip42_challenge_timeout = challenge_timeout ? atoi(challenge_timeout) : 600; - if (challenge_timeout) free((char*)challenge_timeout); - - const char* time_tolerance = get_config_value_from_table("nip42_time_tolerance"); - g_unified_cache.nip42_time_tolerance = time_tolerance ? atoi(time_tolerance) : 300; - if (time_tolerance) free((char*)time_tolerance); - - // Load NIP-70 protected events config - const char* nip70_enabled = get_config_value_from_table("nip70_protected_events_enabled"); - g_unified_cache.nip70_protected_events_enabled = (nip70_enabled && strcmp(nip70_enabled, "true") == 0) ? 1 : 0; - DEBUG_TRACE("Loaded nip70_protected_events_enabled from table: %s (cache value: %d)", - nip70_enabled ? nip70_enabled : "NULL", g_unified_cache.nip70_protected_events_enabled); - if (nip70_enabled) free((char*)nip70_enabled); - - // Load NIP-11 relay info fields directly into cache (avoid circular dependency) - const char* relay_name = get_config_value_from_table("relay_name"); - if (relay_name) { - strncpy(g_unified_cache.relay_info.name, relay_name, sizeof(g_unified_cache.relay_info.name) - 1); - g_unified_cache.relay_info.name[sizeof(g_unified_cache.relay_info.name) - 1] = '\0'; - free((char*)relay_name); - } - - const char* relay_description = get_config_value_from_table("relay_description"); - if (relay_description) { - strncpy(g_unified_cache.relay_info.description, relay_description, sizeof(g_unified_cache.relay_info.description) - 1); - g_unified_cache.relay_info.description[sizeof(g_unified_cache.relay_info.description) - 1] = '\0'; - free((char*)relay_description); - } - - const char* relay_contact = get_config_value_from_table("relay_contact"); - if (relay_contact) { - strncpy(g_unified_cache.relay_info.contact, relay_contact, sizeof(g_unified_cache.relay_info.contact) - 1); - g_unified_cache.relay_info.contact[sizeof(g_unified_cache.relay_info.contact) - 1] = '\0'; - free((char*)relay_contact); - } - - const char* relay_software = get_config_value_from_table("relay_software"); - if (relay_software) { - strncpy(g_unified_cache.relay_info.software, relay_software, sizeof(g_unified_cache.relay_info.software) - 1); - g_unified_cache.relay_info.software[sizeof(g_unified_cache.relay_info.software) - 1] = '\0'; - free((char*)relay_software); - } - - const char* relay_version = get_config_value_from_table("relay_version"); - if (relay_version) { - strncpy(g_unified_cache.relay_info.version, relay_version, sizeof(g_unified_cache.relay_info.version) - 1); - g_unified_cache.relay_info.version[sizeof(g_unified_cache.relay_info.version) - 1] = '\0'; - free((char*)relay_version); - } - - const char* supported_nips = get_config_value_from_table("supported_nips"); - if (supported_nips) { - strncpy(g_unified_cache.relay_info.supported_nips_str, supported_nips, 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'; - free((char*)supported_nips); - } - - const char* language_tags = get_config_value_from_table("language_tags"); - if (language_tags) { - strncpy(g_unified_cache.relay_info.language_tags_str, language_tags, 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'; - free((char*)language_tags); - } - - const char* relay_countries = get_config_value_from_table("relay_countries"); - if (relay_countries) { - strncpy(g_unified_cache.relay_info.relay_countries_str, relay_countries, 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'; - free((char*)relay_countries); - } - - const char* posting_policy = get_config_value_from_table("posting_policy"); - if (posting_policy) { - strncpy(g_unified_cache.relay_info.posting_policy, posting_policy, 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'; - free((char*)posting_policy); - } - - const char* payments_url = get_config_value_from_table("payments_url"); - if (payments_url) { - strncpy(g_unified_cache.relay_info.payments_url, payments_url, 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'; - free((char*)payments_url); - } - - // Set cache expiration - int cache_timeout = get_cache_timeout(); - g_unified_cache.cache_expires = time(NULL) + cache_timeout; - g_unified_cache.cache_valid = 1; - - pthread_mutex_unlock(&g_unified_cache.cache_lock); - - DEBUG_LOG("Configuration cache refreshed (%d entries)", config_count); - DEBUG_LOG("Final cache state - admin_pubkey: %s, relay_pubkey: %s", - g_unified_cache.admin_pubkey[0] ? g_unified_cache.admin_pubkey : "EMPTY", - g_unified_cache.relay_pubkey[0] ? g_unified_cache.relay_pubkey : "EMPTY"); - - // Log config table row count at end of refresh_unified_cache_from_table - if (sqlite3_prepare_v2(g_db, count_sql, -1, &count_stmt, NULL) == SQLITE_OK) { - if (sqlite3_step(count_stmt) == SQLITE_ROW) { - // Row count check completed - } - sqlite3_finalize(count_stmt); - } - - DEBUG_TRACE("Exiting refresh_unified_cache_from_table() - success"); - return 0; -} - - -// Unified cache getter function - handles all config values through unified cache -const char* get_cached_config_value(const char* key) { - if (!key) { - return NULL; - } - - // Check cache validity and refresh if needed - pthread_mutex_lock(&g_unified_cache.cache_lock); - int need_refresh = (!g_unified_cache.cache_valid || time(NULL) > g_unified_cache.cache_expires); - pthread_mutex_unlock(&g_unified_cache.cache_lock); - - if (need_refresh) { - DEBUG_TRACE("Cache refresh needed for key '%s', calling refresh_unified_cache_from_table()", key); - refresh_unified_cache_from_table(); - } - - // Return value from cache based on key - pthread_mutex_lock(&g_unified_cache.cache_lock); - - const char* result = NULL; - - // Handle string fields from unified cache - if (strcmp(key, "admin_pubkey") == 0) { - result = g_unified_cache.admin_pubkey[0] ? g_unified_cache.admin_pubkey : NULL; - DEBUG_TRACE("Returning admin_pubkey from cache: %s", result ? result : "NULL"); - } else if (strcmp(key, "relay_pubkey") == 0) { - result = g_unified_cache.relay_pubkey[0] ? g_unified_cache.relay_pubkey : NULL; - DEBUG_TRACE("Returning relay_pubkey from cache: %s", result ? result : "NULL"); - } else if (strcmp(key, "relay_name") == 0) { - result = g_unified_cache.relay_info.name[0] ? g_unified_cache.relay_info.name : NULL; - DEBUG_TRACE("Returning relay_name from cache: %s", result ? result : "NULL"); - } else if (strcmp(key, "relay_description") == 0) { - result = g_unified_cache.relay_info.description[0] ? g_unified_cache.relay_info.description : NULL; - DEBUG_TRACE("Returning relay_description from cache: %s", result ? result : "NULL"); - } else if (strcmp(key, "relay_contact") == 0) { - result = g_unified_cache.relay_info.contact[0] ? g_unified_cache.relay_info.contact : NULL; - DEBUG_TRACE("Returning relay_contact from cache: %s", result ? result : "NULL"); - } else if (strcmp(key, "relay_software") == 0) { - result = g_unified_cache.relay_info.software[0] ? g_unified_cache.relay_info.software : NULL; - DEBUG_TRACE("Returning relay_software from cache: %s", result ? result : "NULL"); - } else if (strcmp(key, "relay_version") == 0) { - result = g_unified_cache.relay_info.version[0] ? g_unified_cache.relay_info.version : NULL; - DEBUG_TRACE("Returning relay_version from cache: %s", result ? result : "NULL"); - } else if (strcmp(key, "supported_nips") == 0) { - result = g_unified_cache.relay_info.supported_nips_str[0] ? g_unified_cache.relay_info.supported_nips_str : NULL; - DEBUG_TRACE("Returning supported_nips from cache: %s", result ? result : "NULL"); - } else if (strcmp(key, "language_tags") == 0) { - result = g_unified_cache.relay_info.language_tags_str[0] ? g_unified_cache.relay_info.language_tags_str : NULL; - DEBUG_TRACE("Returning language_tags from cache: %s", result ? result : "NULL"); - } else if (strcmp(key, "relay_countries") == 0) { - result = g_unified_cache.relay_info.relay_countries_str[0] ? g_unified_cache.relay_info.relay_countries_str : NULL; - DEBUG_TRACE("Returning relay_countries from cache: %s", result ? result : "NULL"); - } else if (strcmp(key, "posting_policy") == 0) { - result = g_unified_cache.relay_info.posting_policy[0] ? g_unified_cache.relay_info.posting_policy : NULL; - DEBUG_TRACE("Returning posting_policy from cache: %s", result ? result : "NULL"); - } else if (strcmp(key, "payments_url") == 0) { - result = g_unified_cache.relay_info.payments_url[0] ? g_unified_cache.relay_info.payments_url : NULL; - DEBUG_TRACE("Returning payments_url from cache: %s", result ? result : "NULL"); - } else { - DEBUG_TRACE("Key '%s' not found in unified cache", key); - } - - pthread_mutex_unlock(&g_unified_cache.cache_lock); - return result; -} - -// Get integer config value from unified cache -int get_cached_config_int(const char* key, int default_value) { - if (!key) { - return default_value; - } - - // Check cache validity and refresh if needed - pthread_mutex_lock(&g_unified_cache.cache_lock); - int need_refresh = (!g_unified_cache.cache_valid || time(NULL) > g_unified_cache.cache_expires); - pthread_mutex_unlock(&g_unified_cache.cache_lock); - - if (need_refresh) { - DEBUG_TRACE("Cache refresh needed for int key '%s'", key); - refresh_unified_cache_from_table(); - } - - // Return value from cache based on key - pthread_mutex_lock(&g_unified_cache.cache_lock); - - int result = default_value; - - // Handle integer fields from unified cache - if (strcmp(key, "auth_required") == 0) { - result = g_unified_cache.auth_required; - DEBUG_TRACE("Returning auth_required from cache: %d", result); - } else if (strcmp(key, "admin_enabled") == 0) { - result = g_unified_cache.admin_enabled; - DEBUG_TRACE("Returning admin_enabled from cache: %d", result); - } else if (strcmp(key, "max_file_size") == 0) { - result = g_unified_cache.max_file_size; - DEBUG_TRACE("Returning max_file_size from cache: %d", result); - } else if (strcmp(key, "nip42_mode") == 0) { - result = g_unified_cache.nip42_mode; - DEBUG_TRACE("Returning nip42_mode from cache: %d", result); - } else if (strcmp(key, "nip42_challenge_timeout") == 0) { - result = g_unified_cache.nip42_challenge_timeout; - DEBUG_TRACE("Returning nip42_challenge_timeout from cache: %d", result); - } else if (strcmp(key, "nip42_time_tolerance") == 0) { - result = g_unified_cache.nip42_time_tolerance; - DEBUG_TRACE("Returning nip42_time_tolerance from cache: %d", result); - } else if (strcmp(key, "nip70_protected_events_enabled") == 0) { - result = g_unified_cache.nip70_protected_events_enabled; - DEBUG_TRACE("Returning nip70_protected_events_enabled from cache: %d", result); - } else { - DEBUG_TRACE("Integer key '%s' not found in unified cache, using default: %d", key, default_value); - } - - pthread_mutex_unlock(&g_unified_cache.cache_lock); - return result; -} - -// Get boolean config value from unified cache -int get_cached_config_bool(const char* key, int default_value) { - // Boolean values are stored as integers (0/1) in the cache - return get_cached_config_int(key, default_value); -} - - // ================================ // UTILITY FUNCTIONS // ================================ @@ -722,83 +276,34 @@ const char* get_config_value(const char* key) { if (!key) { return NULL; } - - // Try unified cache first for all supported keys - const char* cached_value = get_cached_config_value(key); - if (cached_value) { - return safe_strdup_from_static(cached_value); - } - - // For other keys, try config table directly - const char* table_value = get_config_value_from_table(key); - if (table_value) { - return table_value; - } - - return NULL; + return get_config_value_from_table(key); } int get_config_int(const char* key, int default_value) { - // Try unified cache first for integer values - if (strcmp(key, "auth_required") == 0 || - strcmp(key, "admin_enabled") == 0 || - strcmp(key, "max_file_size") == 0 || - strcmp(key, "nip42_mode") == 0 || - strcmp(key, "nip42_challenge_timeout") == 0 || - strcmp(key, "nip42_time_tolerance") == 0 || - strcmp(key, "nip70_protected_events_enabled") == 0) { - return get_cached_config_int(key, default_value); - } - - // Fall back to string parsing for other keys - const char* str_value = get_config_value(key); - if (!str_value) { + if (!key) { 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); + const char* value = get_config_value_from_table(key); + if (!value) { return default_value; } - - // Free the dynamically allocated string - free((char*)str_value); - return (int)val; + int result = atoi(value); + free((void*)value); + return result; } int get_config_bool(const char* key, int default_value) { - // Try unified cache first for boolean values - if (strcmp(key, "auth_required") == 0 || - strcmp(key, "admin_enabled") == 0 || - strcmp(key, "nip70_protected_events_enabled") == 0) { - return get_cached_config_bool(key, default_value); - } - - // Fall back to string parsing for other keys - const char* str_value = get_config_value(key); - if (!str_value) { + if (!key) { return default_value; } - - int result; - if (strcasecmp(str_value, "true") == 0 || - strcasecmp(str_value, "yes") == 0 || - strcasecmp(str_value, "1") == 0) { - result = 1; - } else if (strcasecmp(str_value, "false") == 0 || - strcasecmp(str_value, "no") == 0 || - strcasecmp(str_value, "0") == 0) { - result = 0; - } else { - result = default_value; + const char* value = get_config_value_from_table(key); + if (!value) { + return default_value; } - - // Free the dynamically allocated string - free((char*)str_value); + int result = (strcmp(value, "1") == 0 || + strcasecmp(value, "true") == 0 || + strcasecmp(value, "yes") == 0) ? 1 : 0; + free((void*)value); return result; } @@ -904,51 +409,9 @@ int init_configuration_system(const char* config_dir_override, const char* confi // Suppress unused parameter warnings for compatibility function (void)config_dir_override; (void)config_file_override; - - // Initialize unified cache with proper structure initialization - pthread_mutex_lock(&g_unified_cache.cache_lock); - // Initialize basic cache state (do not memset entire struct to avoid corrupting mutex) - g_unified_cache.cache_valid = 0; - g_unified_cache.cache_expires = 0; - - // Clear string fields - memset(g_unified_cache.admin_pubkey, 0, sizeof(g_unified_cache.admin_pubkey)); - memset(g_unified_cache.relay_pubkey, 0, sizeof(g_unified_cache.relay_pubkey)); - memset(&g_unified_cache.relay_info, 0, sizeof(g_unified_cache.relay_info)); - - // Initialize relay_info structure with default values - strncpy(g_unified_cache.relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git", - sizeof(g_unified_cache.relay_info.software) - 1); - strncpy(g_unified_cache.relay_info.version, "0.2.0", - sizeof(g_unified_cache.relay_info.version) - 1); - - // Initialize pow_config structure with defaults - g_unified_cache.pow_config.enabled = 1; - g_unified_cache.pow_config.min_pow_difficulty = 0; - g_unified_cache.pow_config.validation_flags = 1; // NOSTR_POW_VALIDATE_BASIC - g_unified_cache.pow_config.require_nonce_tag = 0; - g_unified_cache.pow_config.reject_lower_targets = 0; - g_unified_cache.pow_config.strict_format = 0; - g_unified_cache.pow_config.anti_spam_mode = 0; - - // Initialize expiration_config structure with defaults - g_unified_cache.expiration_config.enabled = 1; - g_unified_cache.expiration_config.strict_mode = 1; - g_unified_cache.expiration_config.filter_responses = 1; - g_unified_cache.expiration_config.delete_expired = 0; - g_unified_cache.expiration_config.grace_period = 1; - - // Initialize other scalar fields - g_unified_cache.auth_required = 0; - g_unified_cache.admin_enabled = 0; - g_unified_cache.max_file_size = 0; - g_unified_cache.nip42_mode = 0; - g_unified_cache.nip42_challenge_timeout = 0; - g_unified_cache.nip42_time_tolerance = 0; - g_unified_cache.nip70_protected_events_enabled = 0; - - pthread_mutex_unlock(&g_unified_cache.cache_lock); + // Configuration system now uses direct database queries instead of cache + // No initialization needed for cache system return 0; } @@ -958,62 +421,14 @@ void cleanup_configuration_system(void) { cJSON_Delete(g_current_config); g_current_config = NULL; } - + if (g_pending_config_event) { cJSON_Delete(g_pending_config_event); g_pending_config_event = NULL; } - - // Clear unified cache with proper cleanup of JSON objects - pthread_mutex_lock(&g_unified_cache.cache_lock); - // Clean up relay_info JSON objects if they exist - if (g_unified_cache.relay_info.supported_nips) { - cJSON_Delete(g_unified_cache.relay_info.supported_nips); - g_unified_cache.relay_info.supported_nips = NULL; - } - if (g_unified_cache.relay_info.limitation) { - cJSON_Delete(g_unified_cache.relay_info.limitation); - g_unified_cache.relay_info.limitation = NULL; - } - if (g_unified_cache.relay_info.retention) { - cJSON_Delete(g_unified_cache.relay_info.retention); - g_unified_cache.relay_info.retention = NULL; - } - if (g_unified_cache.relay_info.relay_countries) { - cJSON_Delete(g_unified_cache.relay_info.relay_countries); - g_unified_cache.relay_info.relay_countries = NULL; - } - if (g_unified_cache.relay_info.language_tags) { - cJSON_Delete(g_unified_cache.relay_info.language_tags); - g_unified_cache.relay_info.language_tags = NULL; - } - if (g_unified_cache.relay_info.tags) { - cJSON_Delete(g_unified_cache.relay_info.tags); - g_unified_cache.relay_info.tags = NULL; - } - if (g_unified_cache.relay_info.fees) { - cJSON_Delete(g_unified_cache.relay_info.fees); - g_unified_cache.relay_info.fees = NULL; - } - - // Clear cache fields individually (do not memset entire struct to avoid corrupting mutex) - g_unified_cache.cache_valid = 0; - g_unified_cache.cache_expires = 0; - memset(g_unified_cache.admin_pubkey, 0, sizeof(g_unified_cache.admin_pubkey)); - memset(g_unified_cache.relay_pubkey, 0, sizeof(g_unified_cache.relay_pubkey)); - memset(&g_unified_cache.relay_info, 0, sizeof(g_unified_cache.relay_info)); - g_unified_cache.auth_required = 0; - g_unified_cache.admin_enabled = 0; - g_unified_cache.max_file_size = 0; - g_unified_cache.nip42_mode = 0; - g_unified_cache.nip42_challenge_timeout = 0; - g_unified_cache.nip42_time_tolerance = 0; - g_unified_cache.nip70_protected_events_enabled = 0; - memset(&g_unified_cache.pow_config, 0, sizeof(g_unified_cache.pow_config)); - memset(&g_unified_cache.expiration_config, 0, sizeof(g_unified_cache.expiration_config)); - - pthread_mutex_unlock(&g_unified_cache.cache_lock); + // Configuration system now uses direct database queries instead of cache + // No cleanup needed for cache system } int set_database_config(const char* key, const char* value, const char* changed_by) { @@ -1247,7 +662,7 @@ cJSON* create_default_config_event(const unsigned char* admin_privkey_bytes, // IMPLEMENTED FUNCTIONS // ================================ -int first_time_startup_sequence(const cli_options_t* cli_options) { +int first_time_startup_sequence(const cli_options_t* cli_options, char* admin_pubkey_out, char* relay_pubkey_out, char* relay_privkey_out) { // 1. Generate or use provided admin keypair unsigned char admin_privkey_bytes[32]; @@ -1292,22 +707,22 @@ int first_time_startup_sequence(const cli_options_t* cli_options) { nostr_bytes_to_hex(admin_pubkey_bytes, 32, admin_pubkey); generated_admin_key = 1; // Generated a new key } - + // 2. Generate or use provided relay keypair unsigned char relay_privkey_bytes[32]; char relay_privkey[65], relay_pubkey[65]; - + if (cli_options && strlen(cli_options->relay_privkey_override) == 64) { // Use provided relay private key strncpy(relay_privkey, cli_options->relay_privkey_override, sizeof(relay_privkey) - 1); relay_privkey[sizeof(relay_privkey) - 1] = '\0'; - + // Convert hex string to bytes if (nostr_hex_to_bytes(relay_privkey, relay_privkey_bytes, 32) != NOSTR_SUCCESS) { DEBUG_ERROR("Failed to convert relay private key hex to bytes"); return -1; } - + // Validate the private key if (nostr_ec_private_key_verify(relay_privkey_bytes) != NOSTR_SUCCESS) { DEBUG_ERROR("Provided relay private key is invalid"); @@ -1321,53 +736,40 @@ int first_time_startup_sequence(const cli_options_t* cli_options) { } nostr_bytes_to_hex(relay_privkey_bytes, 32, relay_privkey); } - + unsigned char relay_pubkey_bytes[32]; if (nostr_ec_public_key_from_private_key(relay_privkey_bytes, relay_pubkey_bytes) != NOSTR_SUCCESS) { DEBUG_ERROR("Failed to derive relay public key"); return -1; } nostr_bytes_to_hex(relay_pubkey_bytes, 32, relay_pubkey); - - // 3. Store keys in unified cache (will be added to database after init) - pthread_mutex_lock(&g_unified_cache.cache_lock); - strncpy(g_unified_cache.admin_pubkey, admin_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1); - g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0'; - 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'; - // Mark cache as valid to prevent premature refresh from empty database - int cache_timeout = get_cache_timeout(); - g_unified_cache.cache_expires = time(NULL) + cache_timeout; - g_unified_cache.cache_valid = 1; - - pthread_mutex_unlock(&g_unified_cache.cache_lock); - // 4. Create database with relay pubkey name if (create_database_with_relay_pubkey(relay_pubkey) != 0) { DEBUG_ERROR("Failed to create database with relay pubkey"); return -1; } - + // 5. Store relay private key in temporary storage for later secure storage strncpy(g_temp_relay_privkey, relay_privkey, sizeof(g_temp_relay_privkey) - 1); g_temp_relay_privkey[sizeof(g_temp_relay_privkey) - 1] = '\0'; - // 6. Populate complete config table with all values atomically - if (populate_all_config_values_atomic(admin_pubkey, relay_pubkey) != 0) { - DEBUG_ERROR("Failed to populate complete config table"); - return -1; - } + // Note: Pubkeys will be stored in database by populate_all_config_values_atomic() + // after database connection is established in main.c - // 7. Apply CLI overrides atomically (after complete config table exists) - if (apply_cli_overrides_atomic(cli_options) != 0) { - DEBUG_ERROR("Failed to apply CLI overrides"); - return -1; + // Copy keys to output parameters + if (admin_pubkey_out) { + strncpy(admin_pubkey_out, admin_pubkey, 64); + admin_pubkey_out[64] = '\0'; + } + if (relay_pubkey_out) { + strncpy(relay_pubkey_out, relay_pubkey, 64); + relay_pubkey_out[64] = '\0'; + } + if (relay_privkey_out) { + strncpy(relay_privkey_out, relay_privkey, 64); + relay_privkey_out[64] = '\0'; } - - // 8. Store relay private key in temporary storage for later secure storage - strncpy(g_temp_relay_privkey, relay_privkey, sizeof(g_temp_relay_privkey) - 1); - g_temp_relay_privkey[sizeof(g_temp_relay_privkey) - 1] = '\0'; // 9. Print admin private key for user to save (only if we generated a new key) if (generated_admin_key) { @@ -1395,7 +797,7 @@ int first_time_startup_sequence(const cli_options_t* cli_options) { printf("=================================================================\n"); printf("\n"); } - + return 0; } @@ -1407,17 +809,7 @@ int startup_existing_relay(const char* relay_pubkey, const cli_options_t* cli_op 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'; - - // Mark cache as valid to prevent premature refresh from database before it's initialized - int cache_timeout = get_cache_timeout(); - g_unified_cache.cache_expires = time(NULL) + cache_timeout; - g_unified_cache.cache_valid = 1; - - pthread_mutex_unlock(&g_unified_cache.cache_lock); + // Relay pubkey is now stored directly in database, no cache needed // Set database path char* db_name = get_database_name_from_relay_pubkey(relay_pubkey); @@ -1449,9 +841,8 @@ int startup_existing_relay(const char* relay_pubkey, const cli_options_t* cli_op return -1; } } else { - // No CLI overrides - load cache immediately from existing config table - DEBUG_INFO("No CLI overrides - loading cache immediately from existing config table"); - refresh_unified_cache_from_table(); + // No CLI overrides - config table is already available + DEBUG_INFO("No CLI overrides - config table is already available"); } return 0; @@ -2101,19 +1492,7 @@ int apply_configuration_from_event(const cJSON* event) { // Update cached configuration g_current_config = cJSON_Duplicate(event, 1); - // Extract admin pubkey if not already in cache - cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey"); - if (pubkey_obj) { - const char* event_pubkey = cJSON_GetStringValue(pubkey_obj); - const char* cached_admin_pubkey = get_config_value("admin_pubkey"); - if (!cached_admin_pubkey || strlen(cached_admin_pubkey) == 0) { - // Update cache with admin pubkey from event - pthread_mutex_lock(&g_unified_cache.cache_lock); - strncpy(g_unified_cache.admin_pubkey, event_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1); - g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0'; - pthread_mutex_unlock(&g_unified_cache.cache_lock); - } - } + // Admin pubkey is now stored directly in database, no cache update needed // Apply runtime configuration changes int handlers_applied = apply_runtime_config_handlers(old_config, g_current_config); @@ -2443,11 +1822,9 @@ int add_pubkeys_to_config_table(void) { DEBUG_INFO("Adding dynamically generated pubkeys to config table..."); - // Get the pubkeys directly from unified cache (not through cached accessors to avoid circular dependency) - pthread_mutex_lock(&g_unified_cache.cache_lock); - const char* admin_pubkey_cache = g_unified_cache.admin_pubkey[0] ? g_unified_cache.admin_pubkey : NULL; - const char* relay_pubkey_cache = g_unified_cache.relay_pubkey[0] ? g_unified_cache.relay_pubkey : NULL; - pthread_mutex_unlock(&g_unified_cache.cache_lock); + // Get pubkeys directly from database (no cache dependency) + const char* admin_pubkey_cache = get_config_value_from_table("admin_pubkey"); + const char* relay_pubkey_cache = get_config_value_from_table("relay_pubkey"); // For existing relays, admin_pubkey might already be in the database but not in cache yet // Try to load it from the database config table first @@ -2459,12 +1836,7 @@ int add_pubkeys_to_config_table(void) { DEBUG_LOG("get_config_value_from_table returned: %s", admin_pubkey_from_db ? admin_pubkey_from_db : "NULL"); if (admin_pubkey_from_db && strlen(admin_pubkey_from_db) == 64) { DEBUG_LOG("Found valid admin_pubkey in config table: %s", admin_pubkey_from_db); - // Update cache with the value from config table - pthread_mutex_lock(&g_unified_cache.cache_lock); - strncpy(g_unified_cache.admin_pubkey, admin_pubkey_from_db, sizeof(g_unified_cache.admin_pubkey) - 1); - g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0'; - DEBUG_TRACE("Loaded admin_pubkey from config table into cache: %s", g_unified_cache.admin_pubkey); - pthread_mutex_unlock(&g_unified_cache.cache_lock); + // Admin pubkey is now stored directly in database, no cache update needed DEBUG_INFO("✓ Loaded admin_pubkey from config table into cache"); // Free the allocated string and return success - pubkey already in table free((char*)admin_pubkey_from_db); @@ -2489,11 +1861,7 @@ int add_pubkeys_to_config_table(void) { if (set_config_value_in_table("admin_pubkey", event_pubkey, "string", "Administrator public key", "authentication", 0) == 0) { - // Update cache - pthread_mutex_lock(&g_unified_cache.cache_lock); - strncpy(g_unified_cache.admin_pubkey, event_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1); - g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0'; - pthread_mutex_unlock(&g_unified_cache.cache_lock); + // Admin pubkey is now stored directly in database, no cache update needed sqlite3_finalize(stmt); DEBUG_INFO("✓ Migrated admin_pubkey from old config event to config table"); @@ -2656,8 +2024,7 @@ int process_admin_config_event(cJSON* event, char* error_message, size_t error_s if (updates_applied > 0) { sqlite3_exec(g_db, "COMMIT", NULL, NULL, NULL); - invalidate_config_cache(); - + char success_msg[256]; snprintf(success_msg, sizeof(success_msg), "Applied %d configuration updates", updates_applied); DEBUG_INFO(success_msg); @@ -3620,8 +2987,6 @@ int handle_config_set_unified(cJSON* event, const char* config_key, const char* return -1; } - // Invalidate cache to ensure fresh reads - invalidate_config_cache(); // Build response cJSON* response = cJSON_CreateObject(); @@ -3801,7 +3166,7 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error cJSON* status_data = cJSON_CreateObject(); cJSON_AddStringToObject(status_data, "database", g_db ? "connected" : "not_available"); - cJSON_AddStringToObject(status_data, "cache_status", g_unified_cache.cache_valid ? "valid" : "invalid"); + cJSON_AddStringToObject(status_data, "cache_status", "not_used"); if (strlen(g_database_path) > 0) { cJSON_AddStringToObject(status_data, "database_path", g_database_path); @@ -3832,7 +3197,7 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error printf("=== System Status ===\n"); printf("Database: %s\n", g_db ? "Connected" : "Not available"); - printf("Cache status: %s\n", g_unified_cache.cache_valid ? "Valid" : "Invalid"); + printf("Cache status: Not used (direct database queries)\n"); // Get admin pubkey from event for response cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey"); @@ -4326,12 +3691,8 @@ int handle_config_update_unified(cJSON* event, char* error_message, size_t error if (update_config_in_table(key, value) == 0) { updates_applied++; - // For dynamic configs (requires_restart = 0), refresh cache immediately + // For dynamic configs (requires_restart = 0), apply selective re-initialization if (requires_restart == 0) { - DEBUG_INFO("Dynamic config updated - refreshing cache"); - refresh_unified_cache_from_table(); - - // Apply selective re-initialization for specific dynamic configs DEBUG_INFO("Applying selective re-initialization for dynamic config changes"); if (strcmp(key, "max_subscriptions_per_client") == 0 || strcmp(key, "max_total_subscriptions") == 0) { @@ -4398,8 +3759,7 @@ int handle_config_update_unified(cJSON* event, char* error_message, size_t error if (updates_applied > 0 && validation_errors == 0) { // All updates successful sqlite3_exec(g_db, "COMMIT", NULL, NULL, NULL); - invalidate_config_cache(); - + char success_msg[256]; snprintf(success_msg, sizeof(success_msg), "Applied %d configuration updates successfully", updates_applied); DEBUG_INFO(success_msg); @@ -4592,8 +3952,7 @@ int apply_cli_overrides_atomic(const cli_options_t* cli_options) { return -1; } - // Invalidate cache to force refresh with new values - invalidate_config_cache(); + // Cache no longer exists - direct database queries are used DEBUG_INFO("Successfully applied CLI overrides atomically"); return 0; @@ -4620,9 +3979,9 @@ int populate_all_config_values_atomic(const char* admin_pubkey, const char* rela return -1; } - // Prepare INSERT statement + // Prepare INSERT OR REPLACE statement with all required fields sqlite3_stmt* stmt = NULL; - const char* sql = "INSERT INTO config (key, value) VALUES (?, ?)"; + const char* sql = "INSERT OR REPLACE INTO config (key, value, data_type, description, category, requires_restart) VALUES (?, ?, ?, ?, ?, ?)"; rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { DEBUG_ERROR("Failed to prepare statement: %s", sqlite3_errmsg(g_db)); @@ -4632,14 +3991,78 @@ int populate_all_config_values_atomic(const char* admin_pubkey, const char* rela // Insert all default config values for (size_t i = 0; i < DEFAULT_CONFIG_COUNT; i++) { + const char* key = DEFAULT_CONFIG_VALUES[i].key; + const char* value = DEFAULT_CONFIG_VALUES[i].value; + + // Determine data type + const char* data_type = "string"; + if (strcmp(key, "relay_port") == 0 || + strcmp(key, "max_connections") == 0 || + strcmp(key, "pow_min_difficulty") == 0 || + strcmp(key, "max_subscriptions_per_client") == 0 || + strcmp(key, "max_total_subscriptions") == 0 || + strcmp(key, "max_filters_per_subscription") == 0 || + strcmp(key, "max_event_tags") == 0 || + strcmp(key, "max_content_length") == 0 || + strcmp(key, "max_message_length") == 0 || + strcmp(key, "default_limit") == 0 || + strcmp(key, "max_limit") == 0 || + strcmp(key, "nip42_challenge_expiration") == 0 || + strcmp(key, "nip40_expiration_grace_period") == 0) { + data_type = "integer"; + } else if (strcmp(key, "auth_enabled") == 0 || + strcmp(key, "nip40_expiration_enabled") == 0 || + strcmp(key, "nip40_expiration_strict") == 0 || + strcmp(key, "nip40_expiration_filter") == 0 || + strcmp(key, "nip42_auth_required_events") == 0 || + strcmp(key, "nip42_auth_required_subscriptions") == 0 || + strcmp(key, "nip70_protected_events_enabled") == 0) { + data_type = "boolean"; + } + + // Set category + const char* category = "general"; + if (strstr(key, "relay_")) { + category = "relay"; + } else if (strstr(key, "nip40_")) { + category = "expiration"; + } else if (strstr(key, "nip42_") || strstr(key, "auth_")) { + category = "authentication"; + } else if (strstr(key, "pow_")) { + category = "proof_of_work"; + } else if (strstr(key, "max_")) { + category = "limits"; + } else if (strstr(key, "nip70_")) { + category = "protected_events"; + } + + // Determine if requires restart (0 = dynamic, 1 = restart required) + int requires_restart = 0; + + // Restart required configs + if (strcmp(key, "relay_port") == 0 || + strcmp(key, "max_connections") == 0 || + strcmp(key, "auth_enabled") == 0 || + strcmp(key, "nip42_auth_required_events") == 0 || + strcmp(key, "nip42_auth_required_subscriptions") == 0 || + strcmp(key, "nip42_auth_required_kinds") == 0 || + strcmp(key, "nip42_challenge_expiration") == 0 || + strcmp(key, "database_path") == 0) { + requires_restart = 1; + } + sqlite3_reset(stmt); - sqlite3_bind_text(stmt, 1, DEFAULT_CONFIG_VALUES[i].key, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, DEFAULT_CONFIG_VALUES[i].value, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, value, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, data_type, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 4, "", -1, SQLITE_STATIC); // description (empty for defaults) + sqlite3_bind_text(stmt, 5, category, -1, SQLITE_STATIC); + sqlite3_bind_int(stmt, 6, requires_restart); rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) { DEBUG_ERROR("Failed to insert config key '%s': %s", - DEFAULT_CONFIG_VALUES[i].key, sqlite3_errmsg(g_db)); + key, sqlite3_errmsg(g_db)); sqlite3_finalize(stmt); sqlite3_exec(g_db, "ROLLBACK;", NULL, NULL, NULL); return -1; @@ -4650,6 +4073,10 @@ int populate_all_config_values_atomic(const char* admin_pubkey, const char* rela sqlite3_reset(stmt); sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, admin_pubkey, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, "string", -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 4, "Administrator public key", -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 5, "authentication", -1, SQLITE_STATIC); + sqlite3_bind_int(stmt, 6, 0); // does not require restart rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) { DEBUG_ERROR("Failed to insert admin_pubkey: %s", sqlite3_errmsg(g_db)); @@ -4662,6 +4089,10 @@ int populate_all_config_values_atomic(const char* admin_pubkey, const char* rela sqlite3_reset(stmt); sqlite3_bind_text(stmt, 1, "relay_pubkey", -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, relay_pubkey, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, "string", -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 4, "Relay public key", -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 5, "relay", -1, SQLITE_STATIC); + sqlite3_bind_int(stmt, 6, 0); // does not require restart rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) { DEBUG_ERROR("Failed to insert relay_pubkey: %s", sqlite3_errmsg(g_db)); @@ -4689,27 +4120,6 @@ int populate_all_config_values_atomic(const char* admin_pubkey, const char* rela // CONFIGURATION CACHE MANAGEMENT // ================================ -// Invalidate configuration cache -void invalidate_config_cache(void) { - pthread_mutex_lock(&g_unified_cache.cache_lock); - g_unified_cache.cache_valid = 0; - g_unified_cache.cache_expires = 0; - pthread_mutex_unlock(&g_unified_cache.cache_lock); - DEBUG_INFO("Unified configuration cache invalidated"); -} - -// Reload configuration from table -int reload_config_from_table(void) { - // Trigger a cache refresh by calling the refresh function directly - int result = refresh_unified_cache_from_table(); - - if (result == 0) { - DEBUG_INFO("Configuration reloaded from table"); - } else { - DEBUG_ERROR("Failed to reload configuration from table"); - } - return result; -} // ================================ // HYBRID CONFIG ACCESS FUNCTIONS @@ -4776,11 +4186,8 @@ int is_config_table_ready(void) { int initialize_config_system_with_migration(void) { DEBUG_INFO("Initializing configuration system with migration support..."); - // Initialize unified cache and migration status - pthread_mutex_lock(&g_unified_cache.cache_lock); - g_unified_cache.cache_valid = 0; - g_unified_cache.cache_expires = 0; - pthread_mutex_unlock(&g_unified_cache.cache_lock); + // Configuration system now uses direct database queries instead of cache + // No cache initialization needed memset(&g_migration_status, 0, sizeof(g_migration_status)); // For new installations, config table should already exist from embedded schema @@ -5026,8 +4433,7 @@ int process_startup_config_event(const cJSON* event) { if (updates_applied > 0) { sqlite3_exec(g_db, "COMMIT", NULL, NULL, NULL); - invalidate_config_cache(); - + char success_msg[256]; snprintf(success_msg, sizeof(success_msg), "Processed startup configuration: %d values updated in config table", updates_applied); diff --git a/src/config.h b/src/config.h index a8a4f1a..0a58028 100644 --- a/src/config.h +++ b/src/config.h @@ -27,78 +27,6 @@ struct lws; // Database path for event-based config extern char g_database_path[512]; -// Unified configuration cache structure (consolidates all caching systems) -typedef struct { - // Critical keys (frequently accessed) - char admin_pubkey[65]; - char relay_pubkey[65]; - - // Auth config (from request_validator) - int auth_required; - long max_file_size; - int admin_enabled; - int nip42_mode; - int nip42_challenge_timeout; - int nip42_time_tolerance; - int nip70_protected_events_enabled; - - // Static buffer for config values (replaces static buffers in get_config_value functions) - char temp_buffer[CONFIG_VALUE_MAX_LENGTH]; - - // NIP-11 relay information (migrated from g_relay_info in main.c) - struct { - char name[RELAY_NAME_MAX_LENGTH]; - char description[RELAY_DESCRIPTION_MAX_LENGTH]; - char banner[RELAY_URL_MAX_LENGTH]; - char icon[RELAY_URL_MAX_LENGTH]; - char pubkey[RELAY_PUBKEY_MAX_LENGTH]; - char contact[RELAY_CONTACT_MAX_LENGTH]; - char software[RELAY_URL_MAX_LENGTH]; - 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; - cJSON* relay_countries; - cJSON* language_tags; - cJSON* tags; - char posting_policy[RELAY_URL_MAX_LENGTH]; - cJSON* fees; - char payments_url[RELAY_URL_MAX_LENGTH]; - } relay_info; - - // NIP-13 PoW configuration (migrated from g_pow_config in main.c) - struct { - int enabled; - int min_pow_difficulty; - int validation_flags; - int require_nonce_tag; - int reject_lower_targets; - int strict_format; - int anti_spam_mode; - } pow_config; - - // NIP-40 Expiration configuration (migrated from g_expiration_config in main.c) - struct { - int enabled; - int strict_mode; - int filter_responses; - int delete_expired; - long grace_period; - } expiration_config; - - // Cache management - time_t cache_expires; - int cache_valid; - pthread_mutex_t cache_lock; -} unified_config_cache_t; - // Command line options structure for first-time startup typedef struct { int port_override; // -1 = not set, >0 = port value @@ -108,9 +36,6 @@ typedef struct { int debug_level; // 0-5, default 0 (no debug output) } cli_options_t; -// Global unified configuration cache -extern unified_config_cache_t g_unified_cache; - // Core configuration functions (temporary compatibility) int init_configuration_system(const char* config_dir_override, const char* config_file_override); void cleanup_configuration_system(void); @@ -138,7 +63,7 @@ int get_config_bool(const char* key, int default_value); // First-time startup functions int is_first_time_startup(void); -int first_time_startup_sequence(const cli_options_t* cli_options); +int first_time_startup_sequence(const cli_options_t* cli_options, char* admin_pubkey_out, char* relay_pubkey_out, char* relay_privkey_out); int startup_existing_relay(const char* relay_pubkey, const cli_options_t* cli_options); // Configuration application functions diff --git a/src/default_config_event.h b/src/default_config_event.h index 7efdd0c..2f55fb6 100644 --- a/src/default_config_event.h +++ b/src/default_config_event.h @@ -28,6 +28,8 @@ static const struct { {"nip42_auth_required_subscriptions", "false"}, {"nip42_auth_required_kinds", "4,14"}, // Default: DM kinds require auth {"nip42_challenge_expiration", "600"}, // 10 minutes + {"nip42_challenge_timeout", "600"}, // Challenge timeout (seconds) + {"nip42_time_tolerance", "300"}, // Time tolerance (seconds) // NIP-70 Protected Events {"nip70_protected_events_enabled", "false"}, diff --git a/src/main.c b/src/main.c index 701470d..8fcf6b7 100644 --- a/src/main.c +++ b/src/main.c @@ -74,10 +74,6 @@ struct relay_info { char payments_url[RELAY_URL_MAX_LENGTH]; }; -// Global relay information instance moved to unified cache -// static struct relay_info g_relay_info = {0}; // REMOVED - now in g_unified_cache.relay_info - - // NIP-40 Expiration configuration (now in nip040.c) extern struct expiration_config g_expiration_config; @@ -92,12 +88,6 @@ extern subscription_manager_t g_subscription_manager; ///////////////////////////////////////////////////////////////////////////////////////// - - - - -// Forward declarations for logging functions - REMOVED (replaced by debug system) - // Forward declaration for subscription manager configuration void update_subscription_manager_config(void); @@ -1273,10 +1263,8 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru cJSON_AddItemToObject(event, "tags", tags); // Check expiration filtering (NIP-40) at application level - pthread_mutex_lock(&g_unified_cache.cache_lock); - int expiration_enabled = g_unified_cache.expiration_config.enabled; - int filter_responses = g_unified_cache.expiration_config.filter_responses; - pthread_mutex_unlock(&g_unified_cache.cache_lock); + int expiration_enabled = get_config_bool("expiration_enabled", 1); + int filter_responses = get_config_bool("expiration_filter", 1); if (expiration_enabled && filter_responses) { time_t current_time = time(NULL); @@ -1632,7 +1620,10 @@ int main(int argc, char* argv[]) { } // Run first-time startup sequence (generates keys, sets up database path, but doesn't store private key yet) - if (first_time_startup_sequence(&cli_options) != 0) { + char admin_pubkey[65] = {0}; + char relay_pubkey[65] = {0}; + char relay_privkey[65] = {0}; + if (first_time_startup_sequence(&cli_options, admin_pubkey, relay_pubkey, relay_privkey) != 0) { DEBUG_ERROR("Failed to complete first-time startup sequence"); cleanup_configuration_system(); nostr_cleanup(); @@ -1662,19 +1653,43 @@ int main(int argc, char* argv[]) { } // DEBUG_GUARD_END + // Now that database is available, populate the complete config table atomically + // BUG FIX: Use the pubkeys returned from first_time_startup_sequence instead of trying to read from empty database + DEBUG_LOG("Using pubkeys from first-time startup sequence for config population"); + DEBUG_LOG("admin_pubkey from startup: %s", admin_pubkey); + DEBUG_LOG("relay_pubkey from startup: %s", relay_pubkey); + + if (populate_all_config_values_atomic(admin_pubkey, relay_pubkey) != 0) { + DEBUG_ERROR("Failed to populate complete config table"); + cleanup_configuration_system(); + nostr_cleanup(); + close_database(); + return 1; + } + + // Apply CLI overrides atomically (after complete config table exists) + if (apply_cli_overrides_atomic(&cli_options) != 0) { + DEBUG_ERROR("Failed to apply CLI overrides"); + cleanup_configuration_system(); + nostr_cleanup(); + close_database(); + return 1; + } + // Now that database is available, store the relay private key securely - const char* relay_privkey = get_temp_relay_private_key(); - if (relay_privkey) { + if (relay_privkey[0] != '\0') { if (store_relay_private_key(relay_privkey) != 0) { DEBUG_ERROR("Failed to store relay private key securely after database initialization"); cleanup_configuration_system(); nostr_cleanup(); + close_database(); return 1; } } else { DEBUG_ERROR("Relay private key not available from first-time startup"); cleanup_configuration_system(); nostr_cleanup(); + close_database(); return 1; } diff --git a/src/nip011.c b/src/nip011.c index 5ae827a..a1888bd 100644 --- a/src/nip011.c +++ b/src/nip011.c @@ -15,8 +15,7 @@ const char* get_config_value(const char* key); int get_config_int(const char* key, int default_value); int get_config_bool(const char* key, int default_value); -// Forward declarations for global cache access -extern unified_config_cache_t g_unified_cache; +// NIP-11 relay information is now managed directly from config table // Forward declarations for constants (defined in config.h and other headers) #define HTTP_STATUS_OK 200 @@ -75,185 +74,14 @@ cJSON* parse_comma_separated_array(const char* csv_string) { // Initialize relay information using configuration system void init_relay_info() { - // Get all config values first (without holding mutex to avoid deadlock) - // Note: These may be dynamically allocated strings that need to be freed - const char* relay_name = get_config_value("relay_name"); - const char* relay_description = get_config_value("relay_description"); - const char* relay_software = get_config_value("relay_software"); - const char* relay_version = get_config_value("relay_version"); - const char* relay_contact = get_config_value("relay_contact"); - const char* relay_pubkey = get_config_value("relay_pubkey"); - const char* supported_nips_csv = get_config_value("supported_nips"); - const char* language_tags_csv = get_config_value("language_tags"); - const char* relay_countries_csv = get_config_value("relay_countries"); - const char* posting_policy = get_config_value("posting_policy"); - const char* payments_url = get_config_value("payments_url"); - - // Get config values for limitations - int max_message_length = get_config_int("max_message_length", 16384); - int max_subscriptions_per_client = get_config_int("max_subscriptions_per_client", 20); - int max_limit = get_config_int("max_limit", 5000); - int max_event_tags = get_config_int("max_event_tags", 100); - int max_content_length = get_config_int("max_content_length", 8196); - int default_limit = get_config_int("default_limit", 500); - int admin_enabled = get_config_bool("admin_enabled", 0); - - pthread_mutex_lock(&g_unified_cache.cache_lock); - - // Update relay information fields - if (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 - } else { - strncpy(g_unified_cache.relay_info.name, "C Nostr Relay", sizeof(g_unified_cache.relay_info.name) - 1); - } - - if (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 - } else { - 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) { - 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 - } else { - 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) { - 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 - } else { - strncpy(g_unified_cache.relay_info.version, "0.2.0", sizeof(g_unified_cache.relay_info.version) - 1); - } - - if (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 - } - - if (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 - } - - if (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 - } - - if (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 - } - - // Initialize supported NIPs array from config - if (supported_nips_csv) { - g_unified_cache.relay_info.supported_nips = parse_comma_separated_array(supported_nips_csv); - free((char*)supported_nips_csv); // Free dynamically allocated string - } else { - // 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 - } - } - - // Initialize server limitations using configuration - g_unified_cache.relay_info.limitation = cJSON_CreateObject(); - if (g_unified_cache.relay_info.limitation) { - 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); - cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH); - cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_event_tags", max_event_tags); - cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_content_length", max_content_length); - cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "min_pow_difficulty", g_unified_cache.pow_config.min_pow_difficulty); - cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "auth_required", admin_enabled ? cJSON_True : cJSON_False); - cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "payment_required", cJSON_False); - cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "restricted_writes", cJSON_False); - 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); - } - - // Initialize empty retention policies (can be configured later) - g_unified_cache.relay_info.retention = cJSON_CreateArray(); - - // Initialize language tags from config - if (language_tags_csv) { - g_unified_cache.relay_info.language_tags = parse_comma_separated_array(language_tags_csv); - free((char*)language_tags_csv); // Free dynamically allocated string - } else { - // 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 from config - if (relay_countries_csv) { - g_unified_cache.relay_info.relay_countries = parse_comma_separated_array(relay_countries_csv); - free((char*)relay_countries_csv); // Free dynamically allocated string - } else { - // 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 - g_unified_cache.relay_info.tags = cJSON_CreateArray(); - - // Initialize fees as empty object (no payment required by default) - g_unified_cache.relay_info.fees = cJSON_CreateObject(); - - pthread_mutex_unlock(&g_unified_cache.cache_lock); + // NIP-11 relay information is now generated dynamically from config table + // No initialization needed - data is fetched directly from database when requested } // Clean up relay information JSON objects void cleanup_relay_info() { - pthread_mutex_lock(&g_unified_cache.cache_lock); - if (g_unified_cache.relay_info.supported_nips) { - cJSON_Delete(g_unified_cache.relay_info.supported_nips); - g_unified_cache.relay_info.supported_nips = NULL; - } - if (g_unified_cache.relay_info.limitation) { - cJSON_Delete(g_unified_cache.relay_info.limitation); - g_unified_cache.relay_info.limitation = NULL; - } - if (g_unified_cache.relay_info.retention) { - cJSON_Delete(g_unified_cache.relay_info.retention); - g_unified_cache.relay_info.retention = NULL; - } - if (g_unified_cache.relay_info.language_tags) { - cJSON_Delete(g_unified_cache.relay_info.language_tags); - g_unified_cache.relay_info.language_tags = NULL; - } - if (g_unified_cache.relay_info.relay_countries) { - cJSON_Delete(g_unified_cache.relay_info.relay_countries); - g_unified_cache.relay_info.relay_countries = NULL; - } - if (g_unified_cache.relay_info.tags) { - cJSON_Delete(g_unified_cache.relay_info.tags); - g_unified_cache.relay_info.tags = NULL; - } - if (g_unified_cache.relay_info.fees) { - cJSON_Delete(g_unified_cache.relay_info.fees); - g_unified_cache.relay_info.fees = NULL; - } - pthread_mutex_unlock(&g_unified_cache.cache_lock); + // NIP-11 relay information is now generated dynamically from config table + // No cleanup needed - data is fetched directly from database when requested } // Generate NIP-11 compliant JSON document @@ -264,248 +92,194 @@ cJSON* generate_relay_info_json() { return NULL; } - pthread_mutex_lock(&g_unified_cache.cache_lock); + // Get all config values directly from database + const char* relay_name = get_config_value("relay_name"); + const char* relay_description = get_config_value("relay_description"); + const char* relay_banner = get_config_value("relay_banner"); + const char* relay_icon = get_config_value("relay_icon"); + const char* relay_pubkey = get_config_value("relay_pubkey"); + const char* relay_contact = get_config_value("relay_contact"); + const char* supported_nips_csv = get_config_value("supported_nips"); + const char* relay_software = get_config_value("relay_software"); + const char* relay_version = get_config_value("relay_version"); + const char* privacy_policy = get_config_value("privacy_policy"); + const char* terms_of_service = get_config_value("terms_of_service"); + const char* posting_policy = get_config_value("posting_policy"); + const char* language_tags_csv = get_config_value("language_tags"); + const char* relay_countries_csv = get_config_value("relay_countries"); + const char* payments_url = get_config_value("payments_url"); - // Defensive reinit: if relay_info appears empty (cache refresh wiped it), rebuild it directly from table - if (strlen(g_unified_cache.relay_info.name) == 0 && - strlen(g_unified_cache.relay_info.description) == 0 && - strlen(g_unified_cache.relay_info.software) == 0) { - DEBUG_WARN("NIP-11 relay_info appears empty, rebuilding directly from config table"); - - // Rebuild relay_info directly from config table to avoid circular cache dependency - // Get values directly from table (similar to init_relay_info but without cache calls) - const char* relay_name = get_config_value_from_table("relay_name"); - if (relay_name) { - strncpy(g_unified_cache.relay_info.name, relay_name, sizeof(g_unified_cache.relay_info.name) - 1); - free((char*)relay_name); - } else { - strncpy(g_unified_cache.relay_info.name, "C Nostr Relay", sizeof(g_unified_cache.relay_info.name) - 1); - } - - const char* relay_description = get_config_value_from_table("relay_description"); - if (relay_description) { - strncpy(g_unified_cache.relay_info.description, relay_description, sizeof(g_unified_cache.relay_info.description) - 1); - free((char*)relay_description); - } else { - 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); - } - - const char* relay_software = get_config_value_from_table("relay_software"); - if (relay_software) { - strncpy(g_unified_cache.relay_info.software, relay_software, sizeof(g_unified_cache.relay_info.software) - 1); - free((char*)relay_software); - } else { - strncpy(g_unified_cache.relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git", sizeof(g_unified_cache.relay_info.software) - 1); - } - - const char* relay_version = get_config_value_from_table("relay_version"); - if (relay_version) { - strncpy(g_unified_cache.relay_info.version, relay_version, sizeof(g_unified_cache.relay_info.version) - 1); - free((char*)relay_version); - } else { - strncpy(g_unified_cache.relay_info.version, "0.2.0", sizeof(g_unified_cache.relay_info.version) - 1); - } - - const char* relay_contact = get_config_value_from_table("relay_contact"); - if (relay_contact) { - strncpy(g_unified_cache.relay_info.contact, relay_contact, sizeof(g_unified_cache.relay_info.contact) - 1); - free((char*)relay_contact); - } - - const char* relay_pubkey = get_config_value_from_table("relay_pubkey"); - if (relay_pubkey) { - strncpy(g_unified_cache.relay_info.pubkey, relay_pubkey, sizeof(g_unified_cache.relay_info.pubkey) - 1); - free((char*)relay_pubkey); - } - - const char* posting_policy = get_config_value_from_table("posting_policy"); - if (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); - } - - const char* payments_url = get_config_value_from_table("payments_url"); - if (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); - } - - // Rebuild supported_nips array - const char* supported_nips_csv = get_config_value_from_table("supported_nips"); - if (supported_nips_csv) { - g_unified_cache.relay_info.supported_nips = parse_comma_separated_array(supported_nips_csv); - free((char*)supported_nips_csv); - } else { - 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)); - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(9)); - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(11)); - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(13)); - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(15)); - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(20)); - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(40)); - cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(42)); - } - } - - // Rebuild limitation object - int max_message_length = 16384; - const char* max_msg_str = get_config_value_from_table("max_message_length"); - if (max_msg_str) { - max_message_length = atoi(max_msg_str); - free((char*)max_msg_str); - } - - int max_subscriptions_per_client = 20; - const char* max_subs_str = get_config_value_from_table("max_subscriptions_per_client"); - if (max_subs_str) { - max_subscriptions_per_client = atoi(max_subs_str); - free((char*)max_subs_str); - } - - int max_limit = 5000; - const char* max_limit_str = get_config_value_from_table("max_limit"); - if (max_limit_str) { - max_limit = atoi(max_limit_str); - free((char*)max_limit_str); - } - - int max_event_tags = 100; - const char* max_tags_str = get_config_value_from_table("max_event_tags"); - if (max_tags_str) { - max_event_tags = atoi(max_tags_str); - free((char*)max_tags_str); - } - - int max_content_length = 8196; - const char* max_content_str = get_config_value_from_table("max_content_length"); - if (max_content_str) { - max_content_length = atoi(max_content_str); - free((char*)max_content_str); - } - - int default_limit = 500; - const char* default_limit_str = get_config_value_from_table("default_limit"); - if (default_limit_str) { - default_limit = atoi(default_limit_str); - free((char*)default_limit_str); - } - - int admin_enabled = 0; - const char* admin_enabled_str = get_config_value_from_table("admin_enabled"); - if (admin_enabled_str) { - admin_enabled = (strcmp(admin_enabled_str, "true") == 0) ? 1 : 0; - free((char*)admin_enabled_str); - } - - g_unified_cache.relay_info.limitation = cJSON_CreateObject(); - if (g_unified_cache.relay_info.limitation) { - 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); - cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH); - cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_event_tags", max_event_tags); - cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_content_length", max_content_length); - cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "min_pow_difficulty", g_unified_cache.pow_config.min_pow_difficulty); - cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "auth_required", admin_enabled ? cJSON_True : cJSON_False); - cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "payment_required", cJSON_False); - cJSON_AddBoolToObject(g_unified_cache.relay_info.limitation, "restricted_writes", cJSON_False); - 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); - } - - // Rebuild other arrays (empty for now) - g_unified_cache.relay_info.retention = cJSON_CreateArray(); - 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("*")); - } - 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("*")); - } - g_unified_cache.relay_info.tags = cJSON_CreateArray(); - g_unified_cache.relay_info.fees = cJSON_CreateObject(); - - } + // Get config values for limitations + int max_message_length = get_config_int("max_message_length", 16384); + int max_subscriptions_per_client = get_config_int("max_subscriptions_per_client", 20); + int max_limit = get_config_int("max_limit", 5000); + int max_event_tags = get_config_int("max_event_tags", 100); + int max_content_length = get_config_int("max_content_length", 8196); + int default_limit = get_config_int("default_limit", 500); + int min_pow_difficulty = get_config_int("pow_min_difficulty", 0); + int admin_enabled = get_config_bool("admin_enabled", 0); // Add basic relay information - if (strlen(g_unified_cache.relay_info.name) > 0) { - cJSON_AddStringToObject(info, "name", g_unified_cache.relay_info.name); + if (relay_name && strlen(relay_name) > 0) { + cJSON_AddStringToObject(info, "name", relay_name); + free((char*)relay_name); + } else { + cJSON_AddStringToObject(info, "name", "C Nostr Relay"); } - if (strlen(g_unified_cache.relay_info.description) > 0) { - cJSON_AddStringToObject(info, "description", g_unified_cache.relay_info.description); + + if (relay_description && strlen(relay_description) > 0) { + cJSON_AddStringToObject(info, "description", relay_description); + free((char*)relay_description); + } else { + cJSON_AddStringToObject(info, "description", "A high-performance Nostr relay implemented in C with SQLite storage"); } - if (strlen(g_unified_cache.relay_info.banner) > 0) { - cJSON_AddStringToObject(info, "banner", g_unified_cache.relay_info.banner); + + if (relay_banner && strlen(relay_banner) > 0) { + cJSON_AddStringToObject(info, "banner", relay_banner); + free((char*)relay_banner); } - if (strlen(g_unified_cache.relay_info.icon) > 0) { - cJSON_AddStringToObject(info, "icon", g_unified_cache.relay_info.icon); + + if (relay_icon && strlen(relay_icon) > 0) { + cJSON_AddStringToObject(info, "icon", relay_icon); + free((char*)relay_icon); } - if (strlen(g_unified_cache.relay_info.pubkey) > 0) { - cJSON_AddStringToObject(info, "pubkey", g_unified_cache.relay_info.pubkey); + + if (relay_pubkey && strlen(relay_pubkey) > 0) { + cJSON_AddStringToObject(info, "pubkey", relay_pubkey); + free((char*)relay_pubkey); } - if (strlen(g_unified_cache.relay_info.contact) > 0) { - cJSON_AddStringToObject(info, "contact", g_unified_cache.relay_info.contact); + + if (relay_contact && strlen(relay_contact) > 0) { + cJSON_AddStringToObject(info, "contact", relay_contact); + free((char*)relay_contact); } - + // Add supported NIPs - if (g_unified_cache.relay_info.supported_nips) { - cJSON_AddItemToObject(info, "supported_nips", cJSON_Duplicate(g_unified_cache.relay_info.supported_nips, 1)); + if (supported_nips_csv && strlen(supported_nips_csv) > 0) { + cJSON* supported_nips = parse_comma_separated_array(supported_nips_csv); + if (supported_nips) { + cJSON_AddItemToObject(info, "supported_nips", supported_nips); + } + free((char*)supported_nips_csv); + } else { + // Default supported NIPs + cJSON* supported_nips = cJSON_CreateArray(); + if (supported_nips) { + cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(1)); // NIP-01: Basic protocol + cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(9)); // NIP-09: Event deletion + cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(11)); // NIP-11: Relay information + cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(13)); // NIP-13: Proof of Work + cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(15)); // NIP-15: EOSE + cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(20)); // NIP-20: Command results + cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(40)); // NIP-40: Expiration Timestamp + cJSON_AddItemToArray(supported_nips, cJSON_CreateNumber(42)); // NIP-42: Authentication + cJSON_AddItemToObject(info, "supported_nips", supported_nips); + } } - + // Add software information - if (strlen(g_unified_cache.relay_info.software) > 0) { - cJSON_AddStringToObject(info, "software", g_unified_cache.relay_info.software); + if (relay_software && strlen(relay_software) > 0) { + cJSON_AddStringToObject(info, "software", relay_software); + free((char*)relay_software); + } else { + cJSON_AddStringToObject(info, "software", "https://git.laantungir.net/laantungir/c-relay.git"); } - if (strlen(g_unified_cache.relay_info.version) > 0) { - cJSON_AddStringToObject(info, "version", g_unified_cache.relay_info.version); + + if (relay_version && strlen(relay_version) > 0) { + cJSON_AddStringToObject(info, "version", relay_version); + free((char*)relay_version); + } else { + cJSON_AddStringToObject(info, "version", "0.2.0"); } - + // Add policies - if (strlen(g_unified_cache.relay_info.privacy_policy) > 0) { - cJSON_AddStringToObject(info, "privacy_policy", g_unified_cache.relay_info.privacy_policy); + if (privacy_policy && strlen(privacy_policy) > 0) { + cJSON_AddStringToObject(info, "privacy_policy", privacy_policy); + free((char*)privacy_policy); } - if (strlen(g_unified_cache.relay_info.terms_of_service) > 0) { - cJSON_AddStringToObject(info, "terms_of_service", g_unified_cache.relay_info.terms_of_service); + + if (terms_of_service && strlen(terms_of_service) > 0) { + cJSON_AddStringToObject(info, "terms_of_service", terms_of_service); + free((char*)terms_of_service); } - if (strlen(g_unified_cache.relay_info.posting_policy) > 0) { - cJSON_AddStringToObject(info, "posting_policy", g_unified_cache.relay_info.posting_policy); + + if (posting_policy && strlen(posting_policy) > 0) { + cJSON_AddStringToObject(info, "posting_policy", posting_policy); + free((char*)posting_policy); } - + // Add server limitations - if (g_unified_cache.relay_info.limitation) { - cJSON_AddItemToObject(info, "limitation", cJSON_Duplicate(g_unified_cache.relay_info.limitation, 1)); + cJSON* limitation = cJSON_CreateObject(); + if (limitation) { + cJSON_AddNumberToObject(limitation, "max_message_length", max_message_length); + cJSON_AddNumberToObject(limitation, "max_subscriptions", max_subscriptions_per_client); + cJSON_AddNumberToObject(limitation, "max_limit", max_limit); + cJSON_AddNumberToObject(limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH); + cJSON_AddNumberToObject(limitation, "max_event_tags", max_event_tags); + cJSON_AddNumberToObject(limitation, "max_content_length", max_content_length); + cJSON_AddNumberToObject(limitation, "min_pow_difficulty", min_pow_difficulty); + cJSON_AddBoolToObject(limitation, "auth_required", admin_enabled ? cJSON_True : cJSON_False); + cJSON_AddBoolToObject(limitation, "payment_required", cJSON_False); + cJSON_AddBoolToObject(limitation, "restricted_writes", cJSON_False); + cJSON_AddNumberToObject(limitation, "created_at_lower_limit", 0); + cJSON_AddNumberToObject(limitation, "created_at_upper_limit", 2147483647); + cJSON_AddNumberToObject(limitation, "default_limit", default_limit); + cJSON_AddItemToObject(info, "limitation", limitation); } - - // Add retention policies if configured - if (g_unified_cache.relay_info.retention && cJSON_GetArraySize(g_unified_cache.relay_info.retention) > 0) { - cJSON_AddItemToObject(info, "retention", cJSON_Duplicate(g_unified_cache.relay_info.retention, 1)); + + // Add retention policies (empty array for now) + cJSON* retention = cJSON_CreateArray(); + if (retention) { + cJSON_AddItemToObject(info, "retention", retention); } - + // Add geographical and language information - if (g_unified_cache.relay_info.relay_countries) { - cJSON_AddItemToObject(info, "relay_countries", cJSON_Duplicate(g_unified_cache.relay_info.relay_countries, 1)); + if (relay_countries_csv && strlen(relay_countries_csv) > 0) { + cJSON* relay_countries = parse_comma_separated_array(relay_countries_csv); + if (relay_countries) { + cJSON_AddItemToObject(info, "relay_countries", relay_countries); + } + free((char*)relay_countries_csv); + } else { + cJSON* relay_countries = cJSON_CreateArray(); + if (relay_countries) { + cJSON_AddItemToArray(relay_countries, cJSON_CreateString("*")); + cJSON_AddItemToObject(info, "relay_countries", relay_countries); + } } - if (g_unified_cache.relay_info.language_tags) { - cJSON_AddItemToObject(info, "language_tags", cJSON_Duplicate(g_unified_cache.relay_info.language_tags, 1)); + + if (language_tags_csv && strlen(language_tags_csv) > 0) { + cJSON* language_tags = parse_comma_separated_array(language_tags_csv); + if (language_tags) { + cJSON_AddItemToObject(info, "language_tags", language_tags); + } + free((char*)language_tags_csv); + } else { + cJSON* language_tags = cJSON_CreateArray(); + if (language_tags) { + cJSON_AddItemToArray(language_tags, cJSON_CreateString("*")); + cJSON_AddItemToObject(info, "language_tags", language_tags); + } } - if (g_unified_cache.relay_info.tags && cJSON_GetArraySize(g_unified_cache.relay_info.tags) > 0) { - cJSON_AddItemToObject(info, "tags", cJSON_Duplicate(g_unified_cache.relay_info.tags, 1)); + + // Add content tags (empty array) + cJSON* tags = cJSON_CreateArray(); + if (tags) { + cJSON_AddItemToObject(info, "tags", tags); } - + // Add payment information if configured - if (strlen(g_unified_cache.relay_info.payments_url) > 0) { - cJSON_AddStringToObject(info, "payments_url", g_unified_cache.relay_info.payments_url); + if (payments_url && strlen(payments_url) > 0) { + cJSON_AddStringToObject(info, "payments_url", payments_url); + free((char*)payments_url); } - if (g_unified_cache.relay_info.fees && cJSON_GetObjectItem(g_unified_cache.relay_info.fees, "admission")) { - cJSON_AddItemToObject(info, "fees", cJSON_Duplicate(g_unified_cache.relay_info.fees, 1)); + + // Add fees (empty object - no payment required by default) + cJSON* fees = cJSON_CreateObject(); + if (fees) { + cJSON_AddItemToObject(info, "fees", fees); } - - pthread_mutex_unlock(&g_unified_cache.cache_lock); - + return info; } diff --git a/src/nip013.c b/src/nip013.c index bdde6c5..e121d1b 100644 --- a/src/nip013.c +++ b/src/nip013.c @@ -10,63 +10,38 @@ #include "config.h" -// NIP-13 PoW configuration structure -struct pow_config { - int enabled; // 0 = disabled, 1 = enabled - int min_pow_difficulty; // Minimum required difficulty (0 = no requirement) - int validation_flags; // Bitflags for validation options - int require_nonce_tag; // 1 = require nonce tag presence - int reject_lower_targets; // 1 = reject if committed < actual difficulty - int strict_format; // 1 = enforce strict nonce tag format - int anti_spam_mode; // 1 = full anti-spam validation -}; +// Configuration functions from config.c +extern int get_config_bool(const char* key, int default_value); +extern int get_config_int(const char* key, int default_value); +extern const char* get_config_value(const char* key); // Initialize PoW configuration using configuration system void init_pow_config() { - - // Get all config values first (without holding mutex to avoid deadlock) - int pow_enabled = get_config_bool("pow_enabled", 1); - int pow_min_difficulty = get_config_int("pow_min_difficulty", 0); - const char* pow_mode = get_config_value("pow_mode"); - - pthread_mutex_lock(&g_unified_cache.cache_lock); - - // Load PoW settings from configuration system - g_unified_cache.pow_config.enabled = pow_enabled; - g_unified_cache.pow_config.min_pow_difficulty = pow_min_difficulty; - - // Configure PoW mode - if (pow_mode) { - if (strcmp(pow_mode, "strict") == 0) { - g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT; - g_unified_cache.pow_config.require_nonce_tag = 1; - g_unified_cache.pow_config.reject_lower_targets = 1; - g_unified_cache.pow_config.strict_format = 1; - g_unified_cache.pow_config.anti_spam_mode = 1; - } else if (strcmp(pow_mode, "full") == 0) { - g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL; - g_unified_cache.pow_config.require_nonce_tag = 1; - } else if (strcmp(pow_mode, "basic") == 0) { - g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC; - } else if (strcmp(pow_mode, "disabled") == 0) { - g_unified_cache.pow_config.enabled = 0; - } - free((char*)pow_mode); // Free dynamically allocated string - } else { - // Default to basic mode - g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC; - } - - pthread_mutex_unlock(&g_unified_cache.cache_lock); + // Configuration is now handled directly through database queries + // No cache initialization needed } // Validate event Proof of Work according to NIP-13 int validate_event_pow(cJSON* event, char* error_message, size_t error_size) { - pthread_mutex_lock(&g_unified_cache.cache_lock); - int enabled = g_unified_cache.pow_config.enabled; - int min_pow_difficulty = g_unified_cache.pow_config.min_pow_difficulty; - int validation_flags = g_unified_cache.pow_config.validation_flags; - pthread_mutex_unlock(&g_unified_cache.cache_lock); + // Get PoW configuration directly from database + int enabled = get_config_bool("pow_enabled", 1); + int min_pow_difficulty = get_config_int("pow_min_difficulty", 0); + const char* pow_mode = get_config_value("pow_mode"); + + // Determine validation flags based on mode + int validation_flags = NOSTR_POW_VALIDATE_BASIC; // Default + if (pow_mode) { + if (strcmp(pow_mode, "strict") == 0) { + validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT; + } else if (strcmp(pow_mode, "full") == 0) { + validation_flags = NOSTR_POW_VALIDATE_FULL; + } else if (strcmp(pow_mode, "basic") == 0) { + validation_flags = NOSTR_POW_VALIDATE_BASIC; + } else if (strcmp(pow_mode, "disabled") == 0) { + enabled = 0; + } + free((char*)pow_mode); + } if (!enabled) { return 0; // PoW validation disabled diff --git a/src/request_validator.c b/src/request_validator.c index 028f752..1247f0a 100644 --- a/src/request_validator.c +++ b/src/request_validator.c @@ -360,11 +360,9 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length) ///////////////////////////////////////////////////////////////////// // 12. NIP-13 Proof of Work validation - pthread_mutex_lock(&g_unified_cache.cache_lock); - int pow_enabled = g_unified_cache.pow_config.enabled; - int pow_min_difficulty = g_unified_cache.pow_config.min_pow_difficulty; - int pow_validation_flags = g_unified_cache.pow_config.validation_flags; - pthread_mutex_unlock(&g_unified_cache.cache_lock); + int pow_enabled = get_config_bool("pow_enabled", 0); + int pow_min_difficulty = get_config_int("pow_min_difficulty", 0); + int pow_validation_flags = get_config_int("pow_validation_flags", 1); if (pow_enabled && pow_min_difficulty > 0) { nostr_pow_result_t pow_result; @@ -505,11 +503,10 @@ void nostr_request_result_free_file_data(nostr_request_result_t *result) { /** - * Force cache refresh - use unified cache system + * Force cache refresh - cache no longer exists, function kept for compatibility */ void nostr_request_validator_force_cache_refresh(void) { - // Use unified cache refresh from config.c - force_config_cache_refresh(); + // Cache no longer exists - direct database queries are used } /** diff --git a/src/sql_schema.h b/src/sql_schema.h index d02a7e9..67a741c 100644 --- a/src/sql_schema.h +++ b/src/sql_schema.h @@ -180,34 +180,6 @@ BEGIN\n\ UPDATE config SET updated_at = strftime('%s', 'now') WHERE key = NEW.key;\n\ END;\n\ \n\ --- Insert default configuration values\n\ -INSERT INTO config (key, value, data_type, description, category, requires_restart) VALUES\n\ - ('relay_description', 'A C Nostr Relay', 'string', 'Relay description', 'general', 0),\n\ - ('relay_contact', '', 'string', 'Relay contact information', 'general', 0),\n\ - ('relay_software', 'https://github.com/laanwj/c-relay', 'string', 'Relay software URL', 'general', 0),\n\ - ('relay_version', '1.0.0', 'string', 'Relay version', 'general', 0),\n\ - ('relay_port', '8888', 'integer', 'Relay port number', 'network', 1),\n\ - ('max_connections', '1000', 'integer', 'Maximum concurrent connections', 'network', 1),\n\ - ('auth_enabled', 'false', 'boolean', 'Enable NIP-42 authentication', 'auth', 0),\n\ - ('nip42_auth_required_events', 'false', 'boolean', 'Require auth for event publishing', 'auth', 0),\n\ - ('nip42_auth_required_subscriptions', 'false', 'boolean', 'Require auth for subscriptions', 'auth', 0),\n\ - ('nip42_auth_required_kinds', '[]', 'json', 'Event kinds requiring authentication', 'auth', 0),\n\ - ('nip42_challenge_expiration', '600', 'integer', 'Auth challenge expiration seconds', 'auth', 0),\n\ - ('pow_min_difficulty', '0', 'integer', 'Minimum proof-of-work difficulty', 'validation', 0),\n\ - ('pow_mode', 'optional', 'string', 'Proof-of-work mode', 'validation', 0),\n\ - ('nip40_expiration_enabled', 'true', 'boolean', 'Enable event expiration', 'validation', 0),\n\ - ('nip40_expiration_strict', 'false', 'boolean', 'Strict expiration mode', 'validation', 0),\n\ - ('nip40_expiration_filter', 'true', 'boolean', 'Filter expired events in queries', 'validation', 0),\n\ - ('nip40_expiration_grace_period', '60', 'integer', 'Expiration grace period seconds', 'validation', 0),\n\ - ('max_subscriptions_per_client', '25', 'integer', 'Maximum subscriptions per client', 'limits', 0),\n\ - ('max_total_subscriptions', '1000', 'integer', 'Maximum total subscriptions', 'limits', 0),\n\ - ('max_filters_per_subscription', '10', 'integer', 'Maximum filters per subscription', 'limits', 0),\n\ - ('max_event_tags', '2000', 'integer', 'Maximum tags per event', 'limits', 0),\n\ - ('max_content_length', '100000', 'integer', 'Maximum event content length', 'limits', 0),\n\ - ('max_message_length', '131072', 'integer', 'Maximum WebSocket message length', 'limits', 0),\n\ - ('default_limit', '100', 'integer', 'Default query limit', 'limits', 0),\n\ - ('max_limit', '5000', 'integer', 'Maximum query limit', 'limits', 0);\n\ -\n\ -- Persistent Subscriptions Logging Tables (Phase 2)\n\ -- Optional database logging for subscription analytics and debugging\n\ \n\ diff --git a/src/subscriptions.c b/src/subscriptions.c index 8cee265..f8a993a 100644 --- a/src/subscriptions.c +++ b/src/subscriptions.c @@ -28,8 +28,8 @@ int validate_search_term(const char* search_term, char* error_message, size_t er // Global database variable extern sqlite3* g_db; -// Global unified cache -extern unified_config_cache_t g_unified_cache; +// Configuration functions from config.c +extern int get_config_bool(const char* key, int default_value); // Global subscription manager extern subscription_manager_t g_subscription_manager; @@ -534,10 +534,8 @@ int broadcast_event_to_subscriptions(cJSON* event) { } // Check if event is expired and should not be broadcast (NIP-40) - pthread_mutex_lock(&g_unified_cache.cache_lock); - int expiration_enabled = g_unified_cache.expiration_config.enabled; - int filter_responses = g_unified_cache.expiration_config.filter_responses; - pthread_mutex_unlock(&g_unified_cache.cache_lock); + int expiration_enabled = get_config_bool("expiration_enabled", 1); + int filter_responses = get_config_bool("expiration_filter", 1); if (expiration_enabled && filter_responses) { time_t current_time = time(NULL); diff --git a/src/websockets.c b/src/websockets.c index 470aee5..cefd166 100644 --- a/src/websockets.c +++ b/src/websockets.c @@ -93,8 +93,8 @@ int validate_filter_array(cJSON* filters, char* error_message, size_t error_size // Forward declarations for NOTICE message support void send_notice_message(struct lws* wsi, const char* message); -// Forward declarations for unified cache access -extern unified_config_cache_t g_unified_cache; +// Configuration functions from config.c +extern int get_config_bool(const char* key, int default_value); // Forward declarations for global state extern sqlite3* g_db; @@ -453,8 +453,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso } if (is_protected_event) { - // Check if protected events are enabled using unified cache - int protected_events_enabled = g_unified_cache.nip70_protected_events_enabled; + // Check if protected events are enabled using config + int protected_events_enabled = get_config_bool("nip70_protected_events_enabled", 0); if (!protected_events_enabled) { // Protected events not supported