From f5bf1cd6eedcfbbaef3f37c9fa81939313e9b633 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 7 Sep 2025 09:49:26 -0400 Subject: [PATCH] Integrate nostr_core_lib unified request validation system - Update nostr_core_lib submodule to latest version with authentication system - Replace ginxsom's local authentication code with nostr_validate_request() API - Remove over 650 lines of redundant authentication logic from main.c - Successfully tested with list and upload operations - Authentication system now shared between ginxsom and ready for c-relay integration --- nostr_core_lib | 2 +- src/main.c | 1071 +++++++++++++++++------------------------------- 2 files changed, 375 insertions(+), 698 deletions(-) diff --git a/nostr_core_lib b/nostr_core_lib index 33129d8..8585e76 160000 --- a/nostr_core_lib +++ b/nostr_core_lib @@ -1 +1 @@ -Subproject commit 33129d82fdce8cff280bc0b5ba7ed5e49531606d +Subproject commit 8585e7649c3bd21f21c7bc6f7934269e36b5737c diff --git a/src/main.c b/src/main.c index 5a5bcbd..736e852 100644 --- a/src/main.c +++ b/src/main.c @@ -16,6 +16,8 @@ #include #include #include "ginxsom.h" +#include "admin_api.h" +#include "../nostr_core_lib/nostr_core/request_validator.h" // Debug macros removed @@ -38,6 +40,264 @@ #define XREASON_AUTH_REQUIRED "Authorization required for upload" #define XREASON_AUTH_INVALID "Invalid or expired authorization" +// Forward declarations for config system +int initialize_server_config(void); +int apply_config_from_event(cJSON* event); +int get_config_file_path(char* path, size_t path_size); +int load_server_config(const char* config_path); +int run_interactive_setup(const char* config_path); + +// Configuration system implementation +#include +#include +#include +#include + +// Server configuration structure +typedef struct { + char admin_pubkey[256]; + char admin_enabled[8]; + int config_loaded; +} server_config_t; + +// Global configuration instance +static server_config_t g_server_config = {0}; + +// Global server private key (stored in memory only for security) +static char server_private_key[128] = {0}; + +// Function to get XDG config directory +const char* get_config_dir(char* buffer, size_t buffer_size) { + const char* xdg_config = getenv("XDG_CONFIG_HOME"); + if (xdg_config) { + snprintf(buffer, buffer_size, "%s/ginxsom", xdg_config); + return buffer; + } + + const char* home = getenv("HOME"); + if (home) { + snprintf(buffer, buffer_size, "%s/.config/ginxsom", home); + return buffer; + } + + // Fallback + return ".config/ginxsom"; +} + +// Load server configuration from database or create defaults +int initialize_server_config(void) { + sqlite3* db = NULL; + sqlite3_stmt* stmt = NULL; + int rc; + + memset(&g_server_config, 0, sizeof(g_server_config)); + + // Open database + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc != SQLITE_OK) { + fprintf(stderr, "CONFIG: Could not open database for config: %s\n", sqlite3_errmsg(db)); + // Config database doesn't exist - leave config uninitialized + g_server_config.config_loaded = 0; + return 0; + } + + // Load admin_pubkey + const char* sql = "SELECT value FROM server_config WHERE key = ?"; + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC); + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + const char* value = (const char*)sqlite3_column_text(stmt, 0); + if (value) { + strncpy(g_server_config.admin_pubkey, value, sizeof(g_server_config.admin_pubkey) - 1); + } + } + sqlite3_finalize(stmt); + } + + // Load admin_enabled + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, "admin_enabled", -1, SQLITE_STATIC); + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + const char* value = (const char*)sqlite3_column_text(stmt, 0); + if (value && strcmp(value, "true") == 0) { + strcpy(g_server_config.admin_enabled, "true"); + } else { + strcpy(g_server_config.admin_enabled, "false"); + } + } + sqlite3_finalize(stmt); + } + + sqlite3_close(db); + + g_server_config.config_loaded = 1; + fprintf(stderr, "CONFIG: Server configuration loaded\n"); + return 1; +} + +// File-based configuration system +// Config file path resolution +int get_config_file_path(char* path, size_t path_size) { + const char* home = getenv("HOME"); + const char* xdg_config = getenv("XDG_CONFIG_HOME"); + + if (xdg_config) { + snprintf(path, path_size, "%s/ginxsom/ginxsom_config_event.json", xdg_config); + } else if (home) { + snprintf(path, path_size, "%s/.config/ginxsom/ginxsom_config_event.json", home); + } else { + return 0; + } + return 1; +} + +// Load and validate config event +int load_server_config(const char* config_path) { + FILE* file = fopen(config_path, "r"); + if (!file) { + return 0; // Config file doesn't exist + } + + // Read entire file + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + char* json_data = malloc(file_size + 1); + if (!json_data) { + fclose(file); + return 0; + } + + fread(json_data, 1, file_size, file); + json_data[file_size] = '\0'; + fclose(file); + + // Parse and validate JSON event + cJSON* event = cJSON_Parse(json_data); + free(json_data); + + if (!event) { + fprintf(stderr, "Invalid JSON in config file\n"); + return 0; + } + + // Validate event structure and signature + if (nostr_validate_event(event) != NOSTR_SUCCESS) { + fprintf(stderr, "Invalid or corrupted config event\n"); + cJSON_Delete(event); + return 0; + } + + // Extract configuration and apply to server + int result = apply_config_from_event(event); + cJSON_Delete(event); + + return result; +} + +// Extract config from validated event and apply to server +int apply_config_from_event(cJSON* event) { + sqlite3* db; + sqlite3_stmt* stmt; + int rc; + + // Open database for config storage + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL); + if (rc) { + fprintf(stderr, "Failed to open database for config\n"); + return 0; + } + + // Extract admin pubkey from event + cJSON* pubkey_json = cJSON_GetObjectItem(event, "pubkey"); + if (!pubkey_json || !cJSON_IsString(pubkey_json)) { + sqlite3_close(db); + return 0; + } + const char* admin_pubkey = cJSON_GetStringValue(pubkey_json); + + // Store admin pubkey in database + const char* insert_sql = "INSERT OR REPLACE INTO server_config (key, value, description) VALUES (?, ?, ?)"; + rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + 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, "Admin public key from config event", -1, SQLITE_STATIC); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + + // Extract server private key and store securely (in memory only) + cJSON* tags = cJSON_GetObjectItem(event, "tags"); + if (tags && cJSON_IsArray(tags)) { + cJSON* tag = NULL; + cJSON_ArrayForEach(tag, tags) { + if (!cJSON_IsArray(tag)) continue; + + cJSON* tag_name = cJSON_GetArrayItem(tag, 0); + cJSON* tag_value = cJSON_GetArrayItem(tag, 1); + + if (!tag_name || !cJSON_IsString(tag_name) || + !tag_value || !cJSON_IsString(tag_value)) continue; + + const char* key = cJSON_GetStringValue(tag_name); + const char* value = cJSON_GetStringValue(tag_value); + + if (strcmp(key, "server_privkey") == 0) { + // Store server private key in global variable (memory only) + strncpy(server_private_key, value, sizeof(server_private_key) - 1); + server_private_key[sizeof(server_private_key) - 1] = '\0'; + } else { + // Store other config values in database + rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, value, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, "From config event", -1, SQLITE_STATIC); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } + } + + sqlite3_close(db); + return 1; +} + +// Interactive setup runner +int run_interactive_setup(const char* config_path) { + printf("\n=== Ginxsom First-Time Setup Required ===\n"); + printf("No configuration found at: %s\n\n", config_path); + printf("Options:\n"); + printf("1. Run interactive setup wizard\n"); + printf("2. Exit and create config manually\n"); + printf("Choice (1/2): "); + + char choice[10]; + if (!fgets(choice, sizeof(choice), stdin)) { + return 1; + } + + if (choice[0] == '1') { + // Run setup script + char script_path[512]; + snprintf(script_path, sizeof(script_path), "./scripts/setup.sh \"%s\"", config_path); + return system(script_path); + } else { + printf("\nManual setup instructions:\n"); + printf("1. Run: ./scripts/generate_config.sh\n"); + printf("2. Place signed config at: %s\n", config_path); + printf("3. Restart ginxsom\n"); + return 1; + } +} + // Function declarations void send_error_response(int status_code, const char* error_type, const char* message, const char* details); void log_request(const char* method, const char* uri, const char* auth_status, int status_code); @@ -494,669 +754,8 @@ int authenticate_request(const char* auth_header, const char* method, const char ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// -// Authentication rule result structure -typedef struct { - int allowed; // 0 = denied, 1 = allowed - char reason[256]; // Human-readable reason - int rule_id; // Rule ID that made the decision (0 if no rule) - int priority; // Priority of the rule that matched -} auth_rule_result_t; - -// Check if authentication rules system is enabled -int auth_rules_enabled(void) { - sqlite3* db; - sqlite3_stmt* stmt; - int rc, enabled = 0; - - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc) { - - return 0; // Disable rules if can't check database - } - - const char* sql = "SELECT value FROM server_config WHERE key = 'auth_rules_enabled'"; - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - const char* value = (const char*)sqlite3_column_text(stmt, 0); - enabled = (value && strcmp(value, "true") == 0) ? 1 : 0; - } - sqlite3_finalize(stmt); - } - sqlite3_close(db); - - return enabled; -} - -// Check pubkey whitelist rule -int check_pubkey_whitelist(const char* pubkey, const char* operation, auth_rule_result_t* result) { - if (!pubkey || !operation || !result) { - return 0; - } - - sqlite3* db; - sqlite3_stmt* stmt; - int rc; - - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc) { - return 0; - } - - const char* sql = "SELECT id, priority, description FROM auth_rules " - "WHERE rule_type = 'pubkey_whitelist' AND rule_target = ? " - "AND (operation = ? OR operation = '*') AND enabled = 1 " - "AND (expires_at IS NULL OR expires_at > strftime('%s', 'now')) " - "ORDER BY priority ASC, created_at ASC LIMIT 1"; - - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { - sqlite3_close(db); - return 0; - } - - sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, operation, -1, SQLITE_STATIC); - - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - result->allowed = 1; - result->rule_id = sqlite3_column_int(stmt, 0); - result->priority = sqlite3_column_int(stmt, 1); - const char* description = (const char*)sqlite3_column_text(stmt, 2); - snprintf(result->reason, sizeof(result->reason), - "Allowed by whitelist rule: %s", description ? description : "pubkey whitelisted"); - - sqlite3_finalize(stmt); - sqlite3_close(db); - return 1; - } - - sqlite3_finalize(stmt); - sqlite3_close(db); - return 0; -} - -// Check pubkey blacklist rule -int check_pubkey_blacklist(const char* pubkey, const char* operation, auth_rule_result_t* result) { - if (!pubkey || !operation || !result) { - return 0; - } - - sqlite3* db; - sqlite3_stmt* stmt; - int rc; - - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc) { - return 0; - } - - const char* sql = "SELECT id, priority, description FROM auth_rules " - "WHERE rule_type = 'pubkey_blacklist' AND rule_target = ? " - "AND (operation = ? OR operation = '*') AND enabled = 1 " - "AND (expires_at IS NULL OR expires_at > strftime('%s', 'now')) " - "ORDER BY priority ASC, created_at ASC LIMIT 1"; - - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { - sqlite3_close(db); - return 0; - } - - sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, operation, -1, SQLITE_STATIC); - - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - result->allowed = 0; - result->rule_id = sqlite3_column_int(stmt, 0); - result->priority = sqlite3_column_int(stmt, 1); - const char* description = (const char*)sqlite3_column_text(stmt, 2); - snprintf(result->reason, sizeof(result->reason), - "Denied by blacklist rule: %s", description ? description : "pubkey blacklisted"); - - sqlite3_finalize(stmt); - sqlite3_close(db); - return 1; - } - - sqlite3_finalize(stmt); - sqlite3_close(db); - return 0; -} - -// Check hash blacklist rule -int check_hash_blacklist(const char* hash, const char* operation, auth_rule_result_t* result) { - if (!hash || !operation || !result) { - return 0; - } - - sqlite3* db; - sqlite3_stmt* stmt; - int rc; - - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc) { - return 0; - } - - const char* sql = "SELECT id, priority, description FROM auth_rules " - "WHERE rule_type = 'hash_blacklist' AND rule_target = ? " - "AND (operation = ? OR operation = '*') AND enabled = 1 " - "AND (expires_at IS NULL OR expires_at > strftime('%s', 'now')) " - "ORDER BY priority ASC, created_at ASC LIMIT 1"; - - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { - sqlite3_close(db); - return 0; - } - - sqlite3_bind_text(stmt, 1, hash, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, operation, -1, SQLITE_STATIC); - - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - result->allowed = 0; - result->rule_id = sqlite3_column_int(stmt, 0); - result->priority = sqlite3_column_int(stmt, 1); - const char* description = (const char*)sqlite3_column_text(stmt, 2); - snprintf(result->reason, sizeof(result->reason), - "Denied by hash blacklist rule: %s", description ? description : "hash blacklisted"); - - sqlite3_finalize(stmt); - sqlite3_close(db); - return 1; - } - - sqlite3_finalize(stmt); - sqlite3_close(db); - return 0; -} - -// Check MIME type whitelist rule -int check_mime_type_whitelist(const char* mime_type, const char* operation, auth_rule_result_t* result) { - if (!mime_type || !operation || !result) { - return 0; - } - - sqlite3* db; - sqlite3_stmt* stmt; - int rc; - - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc) { - return 0; - } - - // Check for exact match or wildcard patterns (e.g., "image/*") - const char* sql = "SELECT id, priority, description FROM auth_rules " - "WHERE rule_type = 'mime_type_whitelist' " - "AND (rule_target = ? OR (rule_target LIKE '%/*' AND ? LIKE REPLACE(rule_target, '*', '%'))) " - "AND (operation = ? OR operation = '*') AND enabled = 1 " - "AND (expires_at IS NULL OR expires_at > strftime('%s', 'now')) " - "ORDER BY priority ASC, created_at ASC LIMIT 1"; - - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { - sqlite3_close(db); - return 0; - } - - sqlite3_bind_text(stmt, 1, mime_type, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, mime_type, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 3, operation, -1, SQLITE_STATIC); - - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - result->allowed = 1; - result->rule_id = sqlite3_column_int(stmt, 0); - result->priority = sqlite3_column_int(stmt, 1); - const char* description = (const char*)sqlite3_column_text(stmt, 2); - snprintf(result->reason, sizeof(result->reason), - "Allowed by MIME type whitelist: %s", description ? description : "MIME type whitelisted"); - - sqlite3_finalize(stmt); - sqlite3_close(db); - return 1; - } - - sqlite3_finalize(stmt); - sqlite3_close(db); - return 0; -} - -// Check MIME type blacklist rule -int check_mime_type_blacklist(const char* mime_type, const char* operation, auth_rule_result_t* result) { - if (!mime_type || !operation || !result) { - return 0; - } - - sqlite3* db; - sqlite3_stmt* stmt; - int rc; - - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc) { - return 0; - } - - // Check for exact match or wildcard patterns (e.g., "application/*") - const char* sql = "SELECT id, priority, description FROM auth_rules " - "WHERE rule_type = 'mime_type_blacklist' " - "AND (rule_target = ? OR (rule_target LIKE '%/*' AND ? LIKE REPLACE(rule_target, '*', '%'))) " - "AND (operation = ? OR operation = '*') AND enabled = 1 " - "AND (expires_at IS NULL OR expires_at > strftime('%s', 'now')) " - "ORDER BY priority ASC, created_at ASC LIMIT 1"; - - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { - sqlite3_close(db); - return 0; - } - - sqlite3_bind_text(stmt, 1, mime_type, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, mime_type, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 3, operation, -1, SQLITE_STATIC); - - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - result->allowed = 0; - result->rule_id = sqlite3_column_int(stmt, 0); - result->priority = sqlite3_column_int(stmt, 1); - const char* description = (const char*)sqlite3_column_text(stmt, 2); - snprintf(result->reason, sizeof(result->reason), - "Denied by MIME type blacklist: %s", description ? description : "MIME type blacklisted"); - - sqlite3_finalize(stmt); - sqlite3_close(db); - return 1; - } - - sqlite3_finalize(stmt); - sqlite3_close(db); - return 0; -} - -// Check file size limit rule -int check_size_limit(long file_size, const char* pubkey, const char* operation, auth_rule_result_t* result) { - if (!result || file_size < 0) { - return 0; - } - - sqlite3* db; - sqlite3_stmt* stmt; - int rc; - - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc) { - return 0; - } - - // Check for pubkey-specific or global size limits - const char* sql = "SELECT id, priority, rule_value, description FROM auth_rules " - "WHERE rule_type = 'size_limit' " - "AND (rule_target = ? OR rule_target = '*') " - "AND (operation = ? OR operation = '*') AND enabled = 1 " - "AND (expires_at IS NULL OR expires_at > strftime('%s', 'now')) " - "ORDER BY CASE WHEN rule_target = ? THEN 0 ELSE 1 END, priority ASC, created_at ASC LIMIT 1"; - - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { - sqlite3_close(db); - return 0; - } - - sqlite3_bind_text(stmt, 1, pubkey ? pubkey : "*", -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, operation, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 3, pubkey ? pubkey : "*", -1, SQLITE_STATIC); - - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - const char* size_limit_str = (const char*)sqlite3_column_text(stmt, 2); - long size_limit = size_limit_str ? atol(size_limit_str) : 0; - - if (size_limit > 0 && file_size > size_limit) { - result->allowed = 0; - result->rule_id = sqlite3_column_int(stmt, 0); - result->priority = sqlite3_column_int(stmt, 1); - const char* description = (const char*)sqlite3_column_text(stmt, 3); - snprintf(result->reason, sizeof(result->reason), - "File size %ld exceeds limit %ld: %s", - file_size, size_limit, description ? description : "size limit exceeded"); - - sqlite3_finalize(stmt); - sqlite3_close(db); - return 1; - } - } - - sqlite3_finalize(stmt); - sqlite3_close(db); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////// -// RULE EVALUATION ENGINE (4.1.3) -///////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////// - -// Cache key generation for authentication decisions -void generate_auth_cache_key(const char* pubkey, const char* operation, const char* hash, - const char* mime_type, long file_size, char* cache_key, size_t key_size) { - char temp_buffer[1024]; - snprintf(temp_buffer, sizeof(temp_buffer), "%s|%s|%s|%s|%ld", - pubkey ? pubkey : "", operation ? operation : "", - hash ? hash : "", mime_type ? mime_type : "", file_size); - - // Generate SHA-256 hash of the key components for consistent cache keys - unsigned char hash_bytes[32]; - if (nostr_sha256((unsigned char*)temp_buffer, strlen(temp_buffer), hash_bytes) == NOSTR_SUCCESS) { - nostr_bytes_to_hex(hash_bytes, 32, cache_key); - cache_key[64] = '\0'; // Ensure null termination - } else { - // Fallback if hashing fails - strncpy(cache_key, temp_buffer, key_size - 1); - cache_key[key_size - 1] = '\0'; - } -} - -// Check authentication cache for previous decisions -int check_auth_cache(const char* cache_key, auth_rule_result_t* result) { - if (!cache_key || !result) { - return 0; - } - - sqlite3* db; - sqlite3_stmt* stmt; - int rc; - - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc) { - return 0; - } - - const char* sql = "SELECT allowed, rule_id, rule_reason FROM auth_cache " - "WHERE cache_key = ? AND expires_at > strftime('%s', 'now')"; - - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { - sqlite3_close(db); - return 0; - } - - sqlite3_bind_text(stmt, 1, cache_key, -1, SQLITE_STATIC); - - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - result->allowed = sqlite3_column_int(stmt, 0); - result->rule_id = sqlite3_column_int(stmt, 1); - result->priority = 0; // Not stored in cache - const char* reason = (const char*)sqlite3_column_text(stmt, 2); - if (reason) { - strncpy(result->reason, reason, sizeof(result->reason) - 1); - result->reason[sizeof(result->reason) - 1] = '\0'; - } - - sqlite3_finalize(stmt); - sqlite3_close(db); - - return 1; - } - - sqlite3_finalize(stmt); - sqlite3_close(db); - return 0; -} - -// Store authentication decision in cache -void store_auth_cache(const char* cache_key, const auth_rule_result_t* result) { - if (!cache_key || !result) { - return; - } - - sqlite3* db; - sqlite3_stmt* stmt; - int rc; - - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL); - if (rc) { - - return; - } - - // Get cache TTL from server config (default 5 minutes) - int cache_ttl = 300; - const char* ttl_sql = "SELECT value FROM server_config WHERE key = 'auth_cache_ttl'"; - rc = sqlite3_prepare_v2(db, ttl_sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - const char* ttl_value = (const char*)sqlite3_column_text(stmt, 0); - if (ttl_value) { - cache_ttl = atoi(ttl_value); - } - } - sqlite3_finalize(stmt); - } - - // Insert or replace cache entry - const char* insert_sql = "INSERT OR REPLACE INTO auth_cache " - "(cache_key, allowed, rule_id, rule_reason, expires_at) " - "VALUES (?, ?, ?, ?, strftime('%s', 'now') + ?)"; - - rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, cache_key, -1, SQLITE_STATIC); - sqlite3_bind_int(stmt, 2, result->allowed); - sqlite3_bind_int(stmt, 3, result->rule_id); - sqlite3_bind_text(stmt, 4, result->reason, -1, SQLITE_STATIC); - sqlite3_bind_int(stmt, 5, cache_ttl); - rc = sqlite3_step(stmt); - sqlite3_finalize(stmt); - } - - sqlite3_close(db); -} - -// Main rule evaluation function -int evaluate_auth_rules(const char* pubkey, const char* operation, const char* hash, - const char* mime_type, long file_size, auth_rule_result_t* result) { - if (!result) { - return 0; - } - - // Initialize result structure - memset(result, 0, sizeof(auth_rule_result_t)); - result->allowed = 1; // Default allow if no rules apply - strcpy(result->reason, "No rules matched - default allow"); - - - // Check if authentication rules system is enabled - if (!auth_rules_enabled()) { - - strcpy(result->reason, "Authentication rules system disabled - default allow"); - return 1; - } - - // Generate cache key for this request - char cache_key[128]; - generate_auth_cache_key(pubkey, operation, hash, mime_type, file_size, cache_key, sizeof(cache_key)); - - // Check cache first for performance - if (check_auth_cache(cache_key, result)) { - - return 1; - } - - - - // Evaluate rules in priority order (lower priority number = higher precedence) - auth_rule_result_t rule_result; - int highest_priority = 9999; - int rule_matched = 0; - - // 1. Check pubkey blacklist first (highest security priority) - if (pubkey && check_pubkey_blacklist(pubkey, operation, &rule_result)) { - if (rule_result.priority < highest_priority) { - *result = rule_result; - highest_priority = rule_result.priority; - rule_matched = 1; - - } - } - - // 2. Check hash blacklist - if (hash && check_hash_blacklist(hash, operation, &rule_result)) { - if (rule_result.priority < highest_priority) { - *result = rule_result; - highest_priority = rule_result.priority; - rule_matched = 1; - - } - } - - // 3. Check MIME type blacklist - if (mime_type && check_mime_type_blacklist(mime_type, operation, &rule_result)) { - if (rule_result.priority < highest_priority) { - *result = rule_result; - highest_priority = rule_result.priority; - rule_matched = 1; - - } - } - - // 4. Check file size limits - if (file_size > 0 && check_size_limit(file_size, pubkey, operation, &rule_result)) { - if (rule_result.priority < highest_priority) { - *result = rule_result; - highest_priority = rule_result.priority; - rule_matched = 1; - - } - } - - // 5. Check pubkey whitelist (only matters if not already denied) - if (pubkey && result->allowed && check_pubkey_whitelist(pubkey, operation, &rule_result)) { - if (rule_result.priority < highest_priority) { - *result = rule_result; - highest_priority = rule_result.priority; - rule_matched = 1; - - } - } - - // 6. Check MIME type whitelist (only if not already denied) - if (mime_type && result->allowed && check_mime_type_whitelist(mime_type, operation, &rule_result)) { - if (rule_result.priority < highest_priority) { - *result = rule_result; - highest_priority = rule_result.priority; - rule_matched = 1; - - } - } - - // Special case: If we have whitelist rules but no whitelist matched, deny by default - if (result->allowed && pubkey) { - sqlite3* db; - sqlite3_stmt* stmt; - int rc; - - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc == SQLITE_OK) { - // Check if any pubkey whitelist rules exist for this operation - const char* sql = "SELECT COUNT(*) FROM auth_rules " - "WHERE rule_type = 'pubkey_whitelist' AND enabled = 1 " - "AND (operation = ? OR operation = '*') " - "AND (expires_at IS NULL OR expires_at > strftime('%s', 'now'))"; - - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, operation, -1, SQLITE_STATIC); - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - int whitelist_count = sqlite3_column_int(stmt, 0); - if (whitelist_count > 0) { - // Whitelist exists but didn't match - deny - result->allowed = 0; - result->rule_id = 0; - result->priority = 0; - snprintf(result->reason, sizeof(result->reason), - "Denied - pubkey not in whitelist (found %d whitelist rules)", whitelist_count); - rule_matched = 1; - - } - } - sqlite3_finalize(stmt); - } - sqlite3_close(db); - } - } - - // Cache the decision for future requests - store_auth_cache(cache_key, result); - - - - return rule_matched; -} - -// Enhanced authentication function that integrates rule evaluation -int authenticate_request_with_rules(const char* auth_header, const char* method, const char* file_hash, - const char* mime_type, long file_size) { - - // Step 1: Basic nostr authentication (if header provided) - const char* pubkey = NULL; - static char pubkey_buffer[256]; - - if (auth_header) { - - // Parse and validate nostr event first - int auth_result = authenticate_request(auth_header, method, file_hash); - if (auth_result != NOSTR_SUCCESS) { - - return auth_result; - } - - - // Extract pubkey from validated event - char event_json[4096]; - int parse_result = parse_authorization_header(auth_header, event_json, sizeof(event_json)); - if (parse_result == NOSTR_SUCCESS) { - cJSON* event = cJSON_Parse(event_json); - if (event) { - cJSON* pubkey_json = cJSON_GetObjectItem(event, "pubkey"); - if (pubkey_json && cJSON_IsString(pubkey_json)) { - const char* temp_pubkey = cJSON_GetStringValue(pubkey_json); - if (temp_pubkey) { - strncpy(pubkey_buffer, temp_pubkey, sizeof(pubkey_buffer)-1); - pubkey_buffer[sizeof(pubkey_buffer)-1] = '\0'; - pubkey = pubkey_buffer; - } - } - cJSON_Delete(event); - } - } - - } else { - - } - - // Step 2: Evaluate authentication rules - auth_rule_result_t rule_result; - int rule_evaluated = evaluate_auth_rules(pubkey, method, file_hash, mime_type, file_size, &rule_result); - - if (rule_evaluated && !rule_result.allowed) { - - return NOSTR_ERROR_INVALID_INPUT; // Authentication denied by rules - } - - - return NOSTR_SUCCESS; -} +// Old authentication system has been replaced with nostr_core_lib unified request validation +// All authentication rules and cache functionality now handled by nostr_validate_request() ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// @@ -1914,8 +1513,21 @@ void handle_head_upload_request(void) { if (auth_header) { // Validate authorization if provided - int auth_result = authenticate_request_with_rules(auth_header, "upload", sha256, content_type, content_length); - if (auth_result != NOSTR_SUCCESS) { + nostr_request_t request = { + .operation = "upload", + .auth_header = auth_header, + .event = NULL, + .resource_hash = sha256, + .mime_type = content_type, + .file_size = content_length, + .client_ip = getenv("REMOTE_ADDR"), + .app_context = NULL + }; + + nostr_request_result_t result; + int auth_result = nostr_validate_request(&request, &result); + + if (auth_result != NOSTR_SUCCESS || !result.valid) { send_upload_error_response(401, "authentication_failed", "Invalid or expired authentication", XREASON_AUTH_INVALID); log_request("HEAD", "/upload", "auth_failed", 401); return; @@ -2288,9 +1900,22 @@ void handle_list_request(const char* pubkey) { if (auth_header) { - int auth_result = authenticate_request_with_rules(auth_header, "list", NULL, NULL, 0); - if (auth_result != NOSTR_SUCCESS) { - send_error_response(401, "authentication_failed", "Invalid or expired authentication", + nostr_request_t request = { + .operation = "list", + .auth_header = auth_header, + .event = NULL, + .resource_hash = NULL, + .mime_type = NULL, + .file_size = 0, + .client_ip = getenv("REMOTE_ADDR"), + .app_context = NULL + }; + + nostr_request_result_t result; + int auth_result = nostr_validate_request(&request, &result); + + if (auth_result != NOSTR_SUCCESS || !result.valid) { + send_error_response(401, "authentication_failed", "Invalid or expired authentication", "The provided Nostr event is invalid, expired, or does not authorize this operation"); log_request("GET", "/list", "failed", 401); return; @@ -2742,9 +2367,22 @@ void handle_delete_request(const char* sha256) { } // Authenticate the request with enhanced rules system - int auth_result = authenticate_request_with_rules(auth_header, "delete", sha256, NULL, 0); - if (auth_result != NOSTR_SUCCESS) { - send_error_response(401, "authentication_failed", "Invalid or expired authentication", + nostr_request_t request = { + .operation = "delete", + .auth_header = auth_header, + .event = NULL, + .resource_hash = sha256, + .mime_type = NULL, + .file_size = 0, + .client_ip = getenv("REMOTE_ADDR"), + .app_context = NULL + }; + + nostr_request_result_t result; + int auth_result = nostr_validate_request(&request, &result); + + if (auth_result != NOSTR_SUCCESS || !result.valid) { + send_error_response(401, "authentication_failed", "Invalid or expired authentication", "The provided Nostr event is invalid, expired, or does not authorize this operation"); log_request("DELETE", "/delete", "failed", 401); return; @@ -3010,13 +2648,28 @@ void handle_upload_request(void) { - // TEMPORARY FIX: Bypass rules system and use simple authentication + // Use new unified request validation system fprintf(stderr, "AUTH: About to perform authentication - auth_header present: %s\r\n", auth_header ? "YES" : "NO"); - int auth_result = NOSTR_SUCCESS; - if (auth_header) { - fprintf(stderr, "AUTH: Calling authenticate_request with hash: %s\r\n", sha256_hex); - auth_result = authenticate_request(auth_header, "upload", sha256_hex); - fprintf(stderr, "AUTH: authenticate_request returned: %d\r\n", auth_result); + + // Create request structure for validation + nostr_request_t request = { + .operation = "upload", + .auth_header = auth_header, + .event = NULL, + .resource_hash = sha256_hex, + .mime_type = content_type, + .file_size = content_length, + .client_ip = getenv("REMOTE_ADDR"), + .app_context = NULL + }; + + nostr_request_result_t result; + int auth_result = nostr_validate_request(&request, &result); + fprintf(stderr, "AUTH: nostr_validate_request returned: %d, valid: %d, reason: %s\r\n", + auth_result, result.valid, result.reason); + + if (auth_result == NOSTR_SUCCESS && !result.valid) { + auth_result = result.error_code; if (auth_result != NOSTR_SUCCESS) { free(file_data); @@ -3069,26 +2722,12 @@ void handle_upload_request(void) { } } - // Extract uploader pubkey from authorization if provided - if (auth_header) { - char event_json[4096]; - int parse_result = parse_authorization_header(auth_header, event_json, sizeof(event_json)); - if (parse_result == NOSTR_SUCCESS) { - cJSON* event = cJSON_Parse(event_json); - if (event) { - cJSON* pubkey_json = cJSON_GetObjectItem(event, "pubkey"); - if (pubkey_json && cJSON_IsString(pubkey_json)) { - static char pubkey_buffer[256]; - const char* temp_pubkey = cJSON_GetStringValue(pubkey_json); - if (temp_pubkey) { - strncpy(pubkey_buffer, temp_pubkey, sizeof(pubkey_buffer)-1); - pubkey_buffer[sizeof(pubkey_buffer)-1] = '\0'; - uploader_pubkey = pubkey_buffer; - } - } - cJSON_Delete(event); - } - } + // Extract uploader pubkey from validation result if auth was provided + if (auth_header && result.pubkey[0]) { + static char pubkey_buffer[256]; + strncpy(pubkey_buffer, result.pubkey, sizeof(pubkey_buffer)-1); + pubkey_buffer[sizeof(pubkey_buffer)-1] = '\0'; + uploader_pubkey = pubkey_buffer; } @@ -3255,6 +2894,33 @@ void handle_upload_request(void) { int main(void) { fprintf(stderr, "STARTUP: FastCGI application starting up\r\n"); fflush(stderr); + + // Initialize server configuration and identity + // Try file-based config first, then fall back to database config + char config_path[512]; + int config_loaded = 0; + + if (get_config_file_path(config_path, sizeof(config_path))) { + fprintf(stderr, "STARTUP: Checking for config file at: %s\n", config_path); + if (load_server_config(config_path)) { + fprintf(stderr, "STARTUP: File-based configuration loaded successfully\n"); + config_loaded = 1; + } else { + fprintf(stderr, "STARTUP: No valid file-based config found, trying database config\n"); + } + } + + // Fall back to database configuration if file config failed + if (!config_loaded && !initialize_server_config()) { + fprintf(stderr, "STARTUP: No configuration found - server starting in setup mode\n"); + fprintf(stderr, "STARTUP: Run interactive setup with: ginxsom --setup\n"); + // For interactive mode (when stdin is available), offer setup + if (isatty(STDIN_FILENO) && get_config_file_path(config_path, sizeof(config_path))) { + return run_interactive_setup(config_path); + } + } else if (!config_loaded) { + fprintf(stderr, "STARTUP: Database configuration loaded successfully\n"); + } // CRITICAL: Initialize nostr crypto system for cryptographic operations fprintf(stderr, "STARTUP: Initializing nostr crypto system...\r\n"); @@ -3263,6 +2929,14 @@ int main(void) { return 1; } fprintf(stderr, "STARTUP: nostr crypto system initialized successfully\r\n"); + + // Initialize request validator system + fprintf(stderr, "STARTUP: Initializing request validator system...\r\n"); + if (nostr_request_validator_init(DB_PATH, "ginxsom") != NOSTR_SUCCESS) { + fprintf(stderr, "FATAL ERROR: Failed to initialize request validator system\r\n"); + return 1; + } + fprintf(stderr, "STARTUP: Request validator system initialized successfully\r\n"); fflush(stderr); while (FCGI_Accept() >= 0) { const char* request_method = getenv("REQUEST_METHOD"); @@ -3301,6 +2975,9 @@ int main(void) { } else if (strcmp(request_method, "PUT") == 0 && strcmp(request_uri, "/report") == 0) { // Handle PUT /report requests (BUD-09) handle_report_request(); + } else if (strncmp(request_uri, "/api/", 5) == 0) { + // Handle admin API requests + handle_admin_api_request(request_method, request_uri); } else if (strcmp(request_method, "GET") == 0 && strncmp(request_uri, "/list/", 6) == 0) { // Handle GET /list/ requests const char* pubkey = request_uri + 6; // Skip "/list/"