904 lines
31 KiB
C
904 lines
31 KiB
C
#include "config.h"
|
|
#include "default_config_event.h"
|
|
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
|
|
// External database connection (from main.c)
|
|
extern sqlite3* g_db;
|
|
|
|
// Global configuration manager instance
|
|
config_manager_t g_config_manager = {0};
|
|
char g_database_path[512] = {0};
|
|
|
|
// Logging functions (defined in main.c)
|
|
extern void log_info(const char* message);
|
|
extern void log_success(const char* message);
|
|
extern void log_warning(const char* message);
|
|
extern void log_error(const char* message);
|
|
|
|
// Current configuration cache
|
|
static cJSON* g_current_config = NULL;
|
|
|
|
// Cache for initial configuration event (before database is initialized)
|
|
static cJSON* g_pending_config_event = NULL;
|
|
|
|
// ================================
|
|
// UTILITY FUNCTIONS
|
|
// ================================
|
|
|
|
char** find_existing_nrdb_files(void) {
|
|
DIR *dir;
|
|
struct dirent *entry;
|
|
char **files = NULL;
|
|
int count = 0;
|
|
|
|
dir = opendir(".");
|
|
if (dir == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// Count .nrdb files
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
if (strstr(entry->d_name, ".nrdb") != NULL) {
|
|
count++;
|
|
}
|
|
}
|
|
rewinddir(dir);
|
|
|
|
if (count == 0) {
|
|
closedir(dir);
|
|
return NULL;
|
|
}
|
|
|
|
// Allocate array for filenames
|
|
files = malloc((count + 1) * sizeof(char*));
|
|
if (!files) {
|
|
closedir(dir);
|
|
return NULL;
|
|
}
|
|
|
|
// Store filenames
|
|
int i = 0;
|
|
while ((entry = readdir(dir)) != NULL && i < count) {
|
|
if (strstr(entry->d_name, ".nrdb") != NULL) {
|
|
files[i] = malloc(strlen(entry->d_name) + 1);
|
|
if (files[i]) {
|
|
strcpy(files[i], entry->d_name);
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
files[i] = NULL; // Null terminate
|
|
|
|
closedir(dir);
|
|
return files;
|
|
}
|
|
|
|
char* extract_pubkey_from_filename(const char* filename) {
|
|
if (!filename) return NULL;
|
|
|
|
// Find .nrdb extension
|
|
const char* dot = strstr(filename, ".nrdb");
|
|
if (!dot) return NULL;
|
|
|
|
// Calculate pubkey length
|
|
size_t pubkey_len = dot - filename;
|
|
if (pubkey_len != 64) return NULL; // Invalid pubkey length
|
|
|
|
// Allocate and copy pubkey
|
|
char* pubkey = malloc(pubkey_len + 1);
|
|
if (!pubkey) return NULL;
|
|
|
|
strncpy(pubkey, filename, pubkey_len);
|
|
pubkey[pubkey_len] = '\0';
|
|
|
|
return pubkey;
|
|
}
|
|
|
|
char* get_database_name_from_relay_pubkey(const char* relay_pubkey) {
|
|
if (!relay_pubkey || strlen(relay_pubkey) != 64) {
|
|
return NULL;
|
|
}
|
|
|
|
char* db_name = malloc(strlen(relay_pubkey) + 6); // +6 for ".nrdb\0"
|
|
if (!db_name) return NULL;
|
|
|
|
sprintf(db_name, "%s.nrdb", relay_pubkey);
|
|
return db_name;
|
|
}
|
|
|
|
// ================================
|
|
// DATABASE FUNCTIONS
|
|
// ================================
|
|
|
|
int create_database_with_relay_pubkey(const char* relay_pubkey) {
|
|
char* db_name = get_database_name_from_relay_pubkey(relay_pubkey);
|
|
if (!db_name) {
|
|
log_error("Failed to generate database name");
|
|
return -1;
|
|
}
|
|
|
|
strncpy(g_database_path, db_name, sizeof(g_database_path) - 1);
|
|
g_database_path[sizeof(g_database_path) - 1] = '\0';
|
|
|
|
log_info("Creating database with relay pubkey");
|
|
printf(" Database: %s\n", db_name);
|
|
|
|
free(db_name);
|
|
return 0;
|
|
}
|
|
|
|
// ================================
|
|
// CONFIGURATION EVENT FUNCTIONS
|
|
// ================================
|
|
|
|
int store_config_event_in_database(const cJSON* event) {
|
|
if (!event || !g_db) {
|
|
return -1;
|
|
}
|
|
|
|
// Get event fields
|
|
cJSON* id_obj = cJSON_GetObjectItem(event, "id");
|
|
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
|
cJSON* created_at_obj = cJSON_GetObjectItem(event, "created_at");
|
|
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
|
cJSON* content_obj = cJSON_GetObjectItem(event, "content");
|
|
cJSON* sig_obj = cJSON_GetObjectItem(event, "sig");
|
|
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
|
|
|
if (!id_obj || !pubkey_obj || !created_at_obj || !kind_obj || !content_obj || !sig_obj || !tags_obj) {
|
|
return -1;
|
|
}
|
|
|
|
// Convert tags to JSON string
|
|
char* tags_str = cJSON_Print(tags_obj);
|
|
if (!tags_str) {
|
|
return -1;
|
|
}
|
|
|
|
// Insert or replace the configuration event (kind 33334 is replaceable)
|
|
const char* sql = "INSERT OR REPLACE INTO events (id, pubkey, created_at, kind, event_type, content, sig, tags) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
|
sqlite3_stmt* stmt;
|
|
|
|
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
log_error("Failed to prepare configuration event insert");
|
|
free(tags_str);
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_bind_text(stmt, 1, cJSON_GetStringValue(id_obj), -1, SQLITE_STATIC);
|
|
sqlite3_bind_text(stmt, 2, cJSON_GetStringValue(pubkey_obj), -1, SQLITE_STATIC);
|
|
sqlite3_bind_int64(stmt, 3, (sqlite3_int64)cJSON_GetNumberValue(created_at_obj));
|
|
sqlite3_bind_int(stmt, 4, (int)cJSON_GetNumberValue(kind_obj));
|
|
sqlite3_bind_text(stmt, 5, "addressable", -1, SQLITE_STATIC); // kind 33334 is addressable
|
|
sqlite3_bind_text(stmt, 6, cJSON_GetStringValue(content_obj), -1, SQLITE_STATIC);
|
|
sqlite3_bind_text(stmt, 7, cJSON_GetStringValue(sig_obj), -1, SQLITE_STATIC);
|
|
sqlite3_bind_text(stmt, 8, tags_str, -1, SQLITE_TRANSIENT);
|
|
|
|
rc = sqlite3_step(stmt);
|
|
sqlite3_finalize(stmt);
|
|
free(tags_str);
|
|
|
|
if (rc == SQLITE_DONE) {
|
|
log_success("Configuration event stored in database");
|
|
return 0;
|
|
} else {
|
|
log_error("Failed to store configuration event");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
cJSON* load_config_event_from_database(const char* relay_pubkey) {
|
|
if (!g_db || !relay_pubkey) {
|
|
return NULL;
|
|
}
|
|
|
|
const char* sql;
|
|
sqlite3_stmt* stmt;
|
|
int rc;
|
|
|
|
// If we have admin pubkey, query by it; otherwise find the most recent kind 33334 event
|
|
if (strlen(g_config_manager.admin_pubkey) > 0) {
|
|
sql = "SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE kind = 33334 AND pubkey = ? ORDER BY created_at DESC LIMIT 1";
|
|
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
log_error("Failed to prepare configuration event query");
|
|
return NULL;
|
|
}
|
|
sqlite3_bind_text(stmt, 1, g_config_manager.admin_pubkey, -1, SQLITE_STATIC);
|
|
} else {
|
|
// During existing relay startup, we don't know the admin pubkey yet
|
|
// Look for any kind 33334 configuration event (should only be one per relay)
|
|
sql = "SELECT id, pubkey, created_at, kind, content, sig, tags FROM events WHERE kind = 33334 ORDER BY created_at DESC LIMIT 1";
|
|
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
log_error("Failed to prepare configuration event query");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
cJSON* event = NULL;
|
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
// Reconstruct the event JSON from database columns
|
|
event = cJSON_CreateObject();
|
|
if (event) {
|
|
const char* event_pubkey = (const char*)sqlite3_column_text(stmt, 1);
|
|
|
|
cJSON_AddStringToObject(event, "id", (const char*)sqlite3_column_text(stmt, 0));
|
|
cJSON_AddStringToObject(event, "pubkey", event_pubkey);
|
|
cJSON_AddNumberToObject(event, "created_at", sqlite3_column_int64(stmt, 2));
|
|
cJSON_AddNumberToObject(event, "kind", sqlite3_column_int(stmt, 3));
|
|
cJSON_AddStringToObject(event, "content", (const char*)sqlite3_column_text(stmt, 4));
|
|
cJSON_AddStringToObject(event, "sig", (const char*)sqlite3_column_text(stmt, 5));
|
|
|
|
// If we didn't have admin pubkey, store it now
|
|
if (strlen(g_config_manager.admin_pubkey) == 0) {
|
|
strncpy(g_config_manager.admin_pubkey, event_pubkey, sizeof(g_config_manager.admin_pubkey) - 1);
|
|
g_config_manager.admin_pubkey[sizeof(g_config_manager.admin_pubkey) - 1] = '\0';
|
|
}
|
|
|
|
// Parse tags JSON
|
|
const char* tags_str = (const char*)sqlite3_column_text(stmt, 6);
|
|
if (tags_str) {
|
|
cJSON* tags = cJSON_Parse(tags_str);
|
|
if (tags) {
|
|
cJSON_AddItemToObject(event, "tags", tags);
|
|
} else {
|
|
cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
|
|
}
|
|
} else {
|
|
cJSON_AddItemToObject(event, "tags", cJSON_CreateArray());
|
|
}
|
|
}
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
return event;
|
|
}
|
|
|
|
// ================================
|
|
// CONFIGURATION ACCESS FUNCTIONS
|
|
// ================================
|
|
|
|
const char* get_config_value(const char* key) {
|
|
static char buffer[CONFIG_VALUE_MAX_LENGTH];
|
|
|
|
if (!key || !g_current_config) {
|
|
return NULL;
|
|
}
|
|
|
|
// Look for key in current configuration tags
|
|
cJSON* tags = cJSON_GetObjectItem(g_current_config, "tags");
|
|
if (!tags || !cJSON_IsArray(tags)) {
|
|
return NULL;
|
|
}
|
|
|
|
cJSON* tag = NULL;
|
|
cJSON_ArrayForEach(tag, tags) {
|
|
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
|
|
cJSON* tag_key = cJSON_GetArrayItem(tag, 0);
|
|
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
|
|
|
|
if (tag_key && tag_value &&
|
|
cJSON_IsString(tag_key) && cJSON_IsString(tag_value)) {
|
|
|
|
if (strcmp(cJSON_GetStringValue(tag_key), key) == 0) {
|
|
strncpy(buffer, cJSON_GetStringValue(tag_value), sizeof(buffer) - 1);
|
|
buffer[sizeof(buffer) - 1] = '\0';
|
|
return buffer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int get_config_int(const char* key, int default_value) {
|
|
const char* str_value = get_config_value(key);
|
|
if (!str_value) {
|
|
return default_value;
|
|
}
|
|
|
|
char* endptr;
|
|
long val = strtol(str_value, &endptr, 10);
|
|
|
|
if (endptr == str_value || *endptr != '\0') {
|
|
return default_value;
|
|
}
|
|
|
|
return (int)val;
|
|
}
|
|
|
|
int get_config_bool(const char* key, int default_value) {
|
|
const char* str_value = get_config_value(key);
|
|
if (!str_value) {
|
|
return default_value;
|
|
}
|
|
|
|
if (strcasecmp(str_value, "true") == 0 ||
|
|
strcasecmp(str_value, "yes") == 0 ||
|
|
strcasecmp(str_value, "1") == 0) {
|
|
return 1;
|
|
} else if (strcasecmp(str_value, "false") == 0 ||
|
|
strcasecmp(str_value, "no") == 0 ||
|
|
strcasecmp(str_value, "0") == 0) {
|
|
return 0;
|
|
}
|
|
|
|
return default_value;
|
|
}
|
|
|
|
// ================================
|
|
// FIRST-TIME STARTUP FUNCTIONS
|
|
// ================================
|
|
|
|
int is_first_time_startup(void) {
|
|
char** existing_files = find_existing_nrdb_files();
|
|
if (existing_files) {
|
|
// Free the array
|
|
for (int i = 0; existing_files[i]; i++) {
|
|
free(existing_files[i]);
|
|
}
|
|
free(existing_files);
|
|
return 0; // Not first time
|
|
}
|
|
return 1; // First time
|
|
}
|
|
|
|
// ================================
|
|
// COMPATIBILITY FUNCTIONS
|
|
// ================================
|
|
|
|
int init_configuration_system(const char* config_dir_override, const char* config_file_override) {
|
|
// Suppress unused parameter warnings for compatibility function
|
|
(void)config_dir_override;
|
|
(void)config_file_override;
|
|
|
|
log_info("Initializing event-based configuration system...");
|
|
|
|
// Clear configuration manager state
|
|
memset(&g_config_manager, 0, sizeof(config_manager_t));
|
|
g_config_manager.db = g_db;
|
|
|
|
// For now, set empty paths for compatibility
|
|
g_config_manager.config_file_path[0] = '\0';
|
|
|
|
log_success("Event-based configuration system initialized");
|
|
return 0;
|
|
}
|
|
|
|
void cleanup_configuration_system(void) {
|
|
log_info("Cleaning up configuration system...");
|
|
|
|
if (g_current_config) {
|
|
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;
|
|
}
|
|
|
|
memset(&g_config_manager, 0, sizeof(config_manager_t));
|
|
log_success("Configuration system cleaned up");
|
|
}
|
|
|
|
int set_database_config(const char* key, const char* value, const char* changed_by) {
|
|
// Suppress unused parameter warnings for compatibility function
|
|
(void)key;
|
|
(void)value;
|
|
(void)changed_by;
|
|
|
|
// Temporary compatibility function - does nothing for now
|
|
// In the new system, configuration is only updated via events
|
|
log_warning("set_database_config called - not supported in event-based config");
|
|
return 0;
|
|
}
|
|
|
|
// ================================
|
|
// KEY GENERATION FUNCTIONS
|
|
// ================================
|
|
|
|
// Helper function to generate random private key
|
|
int generate_random_private_key_bytes(unsigned char* privkey_bytes) {
|
|
if (!privkey_bytes) {
|
|
return -1;
|
|
}
|
|
|
|
FILE* urandom = fopen("/dev/urandom", "rb");
|
|
if (!urandom) {
|
|
log_error("Failed to open /dev/urandom for key generation");
|
|
return -1;
|
|
}
|
|
|
|
if (fread(privkey_bytes, 1, 32, urandom) != 32) {
|
|
log_error("Failed to read random bytes for private key");
|
|
fclose(urandom);
|
|
return -1;
|
|
}
|
|
fclose(urandom);
|
|
|
|
// Verify the private key is valid using nostr_core_lib
|
|
if (nostr_ec_private_key_verify(privkey_bytes) != NOSTR_SUCCESS) {
|
|
log_error("Generated private key failed validation");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// ================================
|
|
// DEFAULT CONFIG EVENT CREATION
|
|
// ================================
|
|
|
|
cJSON* create_default_config_event(const unsigned char* admin_privkey_bytes,
|
|
const char* relay_privkey_hex,
|
|
const char* relay_pubkey_hex) {
|
|
if (!admin_privkey_bytes || !relay_privkey_hex || !relay_pubkey_hex) {
|
|
log_error("Invalid parameters for creating default config event");
|
|
return NULL;
|
|
}
|
|
|
|
log_info("Creating default configuration event...");
|
|
|
|
// Create tags array with default configuration values
|
|
cJSON* tags = cJSON_CreateArray();
|
|
if (!tags) {
|
|
log_error("Failed to create tags array");
|
|
return NULL;
|
|
}
|
|
|
|
// Add d tag with relay pubkey
|
|
cJSON* d_tag = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(d_tag, cJSON_CreateString("d"));
|
|
cJSON_AddItemToArray(d_tag, cJSON_CreateString(relay_pubkey_hex));
|
|
cJSON_AddItemToArray(tags, d_tag);
|
|
|
|
// Add relay keys
|
|
cJSON* relay_pubkey_tag = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(relay_pubkey_tag, cJSON_CreateString("relay_pubkey"));
|
|
cJSON_AddItemToArray(relay_pubkey_tag, cJSON_CreateString(relay_pubkey_hex));
|
|
cJSON_AddItemToArray(tags, relay_pubkey_tag);
|
|
|
|
cJSON* relay_privkey_tag = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(relay_privkey_tag, cJSON_CreateString("relay_privkey"));
|
|
cJSON_AddItemToArray(relay_privkey_tag, cJSON_CreateString(relay_privkey_hex));
|
|
cJSON_AddItemToArray(tags, relay_privkey_tag);
|
|
|
|
// Add all default configuration values
|
|
for (size_t i = 0; i < DEFAULT_CONFIG_COUNT; i++) {
|
|
cJSON* tag = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(tag, cJSON_CreateString(DEFAULT_CONFIG_VALUES[i].key));
|
|
cJSON_AddItemToArray(tag, cJSON_CreateString(DEFAULT_CONFIG_VALUES[i].value));
|
|
cJSON_AddItemToArray(tags, tag);
|
|
}
|
|
|
|
// Create and sign event using nostr_core_lib
|
|
cJSON* event = nostr_create_and_sign_event(
|
|
33334, // kind
|
|
"C Nostr Relay Configuration", // content
|
|
tags, // tags
|
|
admin_privkey_bytes, // private key bytes for signing
|
|
time(NULL) // created_at timestamp
|
|
);
|
|
|
|
cJSON_Delete(tags); // Clean up tags as they were duplicated in nostr_create_and_sign_event
|
|
|
|
if (!event) {
|
|
log_error("Failed to create and sign configuration event");
|
|
return NULL;
|
|
}
|
|
|
|
// Log success information
|
|
cJSON* id_obj = cJSON_GetObjectItem(event, "id");
|
|
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
|
|
|
if (id_obj && pubkey_obj) {
|
|
log_success("Default configuration event created successfully");
|
|
printf(" Event ID: %s\n", cJSON_GetStringValue(id_obj));
|
|
printf(" Admin Public Key: %s\n", cJSON_GetStringValue(pubkey_obj));
|
|
}
|
|
|
|
return event;
|
|
}
|
|
|
|
// ================================
|
|
// IMPLEMENTED FUNCTIONS
|
|
// ================================
|
|
|
|
int first_time_startup_sequence(void) {
|
|
log_info("Starting first-time startup sequence...");
|
|
|
|
// 1. Generate admin keypair using /dev/urandom + nostr_core_lib
|
|
unsigned char admin_privkey_bytes[32];
|
|
char admin_privkey[65], admin_pubkey[65];
|
|
|
|
if (generate_random_private_key_bytes(admin_privkey_bytes) != 0) {
|
|
log_error("Failed to generate admin private key");
|
|
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) != NOSTR_SUCCESS) {
|
|
log_error("Failed to derive admin public key");
|
|
return -1;
|
|
}
|
|
nostr_bytes_to_hex(admin_pubkey_bytes, 32, admin_pubkey);
|
|
|
|
// 2. Generate relay keypair using /dev/urandom + nostr_core_lib
|
|
unsigned char relay_privkey_bytes[32];
|
|
char relay_privkey[65], relay_pubkey[65];
|
|
|
|
if (generate_random_private_key_bytes(relay_privkey_bytes) != 0) {
|
|
log_error("Failed to generate relay private key");
|
|
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) != NOSTR_SUCCESS) {
|
|
log_error("Failed to derive relay public key");
|
|
return -1;
|
|
}
|
|
nostr_bytes_to_hex(relay_pubkey_bytes, 32, relay_pubkey);
|
|
|
|
// 3. Store keys in global config manager
|
|
strncpy(g_config_manager.admin_pubkey, admin_pubkey, sizeof(g_config_manager.admin_pubkey) - 1);
|
|
g_config_manager.admin_pubkey[sizeof(g_config_manager.admin_pubkey) - 1] = '\0';
|
|
strncpy(g_config_manager.relay_pubkey, relay_pubkey, sizeof(g_config_manager.relay_pubkey) - 1);
|
|
g_config_manager.relay_pubkey[sizeof(g_config_manager.relay_pubkey) - 1] = '\0';
|
|
|
|
// 4. Create database with relay pubkey name
|
|
if (create_database_with_relay_pubkey(relay_pubkey) != 0) {
|
|
log_error("Failed to create database with relay pubkey");
|
|
return -1;
|
|
}
|
|
|
|
// 5. Create initial configuration event using defaults
|
|
cJSON* config_event = create_default_config_event(admin_privkey_bytes, relay_privkey, relay_pubkey);
|
|
if (!config_event) {
|
|
log_error("Failed to create default configuration event");
|
|
return -1;
|
|
}
|
|
|
|
// 6. Try to store configuration event in database, but cache it if database isn't ready
|
|
if (store_config_event_in_database(config_event) == 0) {
|
|
log_success("Initial configuration event stored successfully");
|
|
} else {
|
|
log_warning("Failed to store initial configuration event - will retry after database init");
|
|
// Cache the event for later storage
|
|
if (g_pending_config_event) {
|
|
cJSON_Delete(g_pending_config_event);
|
|
}
|
|
g_pending_config_event = cJSON_Duplicate(config_event, 1);
|
|
}
|
|
|
|
// 7. Cache the current config
|
|
if (g_current_config) {
|
|
cJSON_Delete(g_current_config);
|
|
}
|
|
g_current_config = cJSON_Duplicate(config_event, 1);
|
|
|
|
// 8. Clean up
|
|
cJSON_Delete(config_event);
|
|
|
|
// 9. Print admin private key for user to save
|
|
printf("\n");
|
|
printf("=================================================================\n");
|
|
printf("IMPORTANT: SAVE THIS ADMIN PRIVATE KEY SECURELY!\n");
|
|
printf("=================================================================\n");
|
|
printf("Admin Private Key: %s\n", admin_privkey);
|
|
printf("Admin Public Key: %s\n", admin_pubkey);
|
|
printf("\nRelay Private Key: %s\n", relay_privkey);
|
|
printf("Relay Public Key: %s\n", relay_pubkey);
|
|
printf("\nDatabase: %s\n", g_database_path);
|
|
printf("\nThis admin private key is needed to update configuration!\n");
|
|
printf("Store it safely - it will not be displayed again.\n");
|
|
printf("=================================================================\n");
|
|
printf("\n");
|
|
|
|
log_success("First-time startup sequence completed");
|
|
return 0;
|
|
}
|
|
|
|
int startup_existing_relay(const char* relay_pubkey) {
|
|
if (!relay_pubkey) {
|
|
log_error("Invalid relay pubkey for existing relay startup");
|
|
return -1;
|
|
}
|
|
|
|
log_info("Starting existing relay...");
|
|
printf(" Relay pubkey: %s\n", relay_pubkey);
|
|
|
|
// Store relay pubkey in global config manager
|
|
strncpy(g_config_manager.relay_pubkey, relay_pubkey, sizeof(g_config_manager.relay_pubkey) - 1);
|
|
|
|
// Set database path
|
|
char* db_name = get_database_name_from_relay_pubkey(relay_pubkey);
|
|
if (!db_name) {
|
|
log_error("Failed to generate database name");
|
|
return -1;
|
|
}
|
|
|
|
strncpy(g_database_path, db_name, sizeof(g_database_path) - 1);
|
|
g_database_path[sizeof(g_database_path) - 1] = '\0';
|
|
free(db_name);
|
|
|
|
// Load configuration event from database (after database is initialized)
|
|
// This will be done in apply_configuration_from_database()
|
|
|
|
log_success("Existing relay startup prepared");
|
|
return 0;
|
|
}
|
|
|
|
int process_configuration_event(const cJSON* event) {
|
|
if (!event) {
|
|
log_error("Invalid configuration event");
|
|
return -1;
|
|
}
|
|
|
|
log_info("Processing configuration event...");
|
|
|
|
// Validate event structure
|
|
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
|
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
|
|
|
if (!kind_obj || cJSON_GetNumberValue(kind_obj) != 33334) {
|
|
log_error("Invalid event kind for configuration");
|
|
return -1;
|
|
}
|
|
|
|
if (!pubkey_obj) {
|
|
log_error("Missing pubkey in configuration event");
|
|
return -1;
|
|
}
|
|
|
|
// Verify it's from the admin
|
|
const char* event_pubkey = cJSON_GetStringValue(pubkey_obj);
|
|
if (strlen(g_config_manager.admin_pubkey) > 0) {
|
|
if (strcmp(event_pubkey, g_config_manager.admin_pubkey) != 0) {
|
|
log_error("Configuration event not from authorized admin");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Comprehensive event validation using nostr_core_lib
|
|
log_info("Validating configuration event structure and signature...");
|
|
|
|
// First validate the event structure (fields, format, etc.)
|
|
if (nostr_validate_event_structure((cJSON*)event) != NOSTR_SUCCESS) {
|
|
log_error("Configuration event has invalid structure");
|
|
return -1;
|
|
}
|
|
|
|
// Then validate the cryptographic signature
|
|
if (nostr_verify_event_signature((cJSON*)event) != NOSTR_SUCCESS) {
|
|
log_error("Configuration event has invalid signature");
|
|
return -1;
|
|
}
|
|
|
|
log_success("Configuration event structure and signature validated successfully");
|
|
|
|
// Store in database
|
|
if (store_config_event_in_database(event) != 0) {
|
|
log_error("Failed to store configuration event");
|
|
return -1;
|
|
}
|
|
|
|
// Apply configuration
|
|
if (apply_configuration_from_event(event) != 0) {
|
|
log_error("Failed to apply configuration from event");
|
|
return -1;
|
|
}
|
|
|
|
log_success("Configuration event processed successfully");
|
|
return 0;
|
|
}
|
|
|
|
// ================================
|
|
// RUNTIME CONFIGURATION HANDLERS
|
|
// ================================
|
|
|
|
// External functions and globals from main.c that need to be updated
|
|
extern void update_subscription_manager_config(void);
|
|
extern void init_pow_config(void);
|
|
extern void init_expiration_config(void);
|
|
extern void init_relay_info(void);
|
|
|
|
// Compare configuration values between old and new config
|
|
static const char* get_config_value_from_event(const cJSON* event, const char* key) {
|
|
if (!event || !key) return NULL;
|
|
|
|
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
|
if (!tags || !cJSON_IsArray(tags)) return NULL;
|
|
|
|
cJSON* tag = NULL;
|
|
cJSON_ArrayForEach(tag, tags) {
|
|
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
|
|
cJSON* tag_key = cJSON_GetArrayItem(tag, 0);
|
|
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
|
|
|
|
if (tag_key && tag_value &&
|
|
cJSON_IsString(tag_key) && cJSON_IsString(tag_value)) {
|
|
if (strcmp(cJSON_GetStringValue(tag_key), key) == 0) {
|
|
return cJSON_GetStringValue(tag_value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Check if a configuration value has changed
|
|
static int config_value_changed(const cJSON* old_config, const cJSON* new_config, const char* key) {
|
|
const char* old_value = get_config_value_from_event(old_config, key);
|
|
const char* new_value = get_config_value_from_event(new_config, key);
|
|
|
|
// Both NULL - no change
|
|
if (!old_value && !new_value) return 0;
|
|
|
|
// One is NULL, other isn't - changed
|
|
if (!old_value || !new_value) return 1;
|
|
|
|
// Compare string values
|
|
return strcmp(old_value, new_value) != 0;
|
|
}
|
|
|
|
// Apply runtime configuration changes by calling appropriate handlers
|
|
int apply_runtime_config_handlers(const cJSON* old_config, const cJSON* new_config) {
|
|
if (!new_config) return 0;
|
|
|
|
int handlers_applied = 0;
|
|
|
|
log_info("Checking for runtime configuration changes...");
|
|
|
|
// Subscription Manager Configuration
|
|
if (config_value_changed(old_config, new_config, "max_subscriptions_per_client") ||
|
|
config_value_changed(old_config, new_config, "max_total_subscriptions")) {
|
|
log_info("Subscription limits changed - updating subscription manager");
|
|
update_subscription_manager_config();
|
|
handlers_applied++;
|
|
}
|
|
|
|
// NIP-13 Proof of Work Configuration
|
|
if (config_value_changed(old_config, new_config, "pow_min_difficulty") ||
|
|
config_value_changed(old_config, new_config, "pow_mode")) {
|
|
log_info("PoW configuration changed - reinitializing PoW system");
|
|
init_pow_config();
|
|
handlers_applied++;
|
|
}
|
|
|
|
// NIP-40 Expiration Configuration
|
|
if (config_value_changed(old_config, new_config, "nip40_expiration_enabled") ||
|
|
config_value_changed(old_config, new_config, "nip40_expiration_strict") ||
|
|
config_value_changed(old_config, new_config, "nip40_expiration_filter") ||
|
|
config_value_changed(old_config, new_config, "nip40_expiration_grace_period")) {
|
|
log_info("Expiration configuration changed - reinitializing expiration system");
|
|
init_expiration_config();
|
|
handlers_applied++;
|
|
}
|
|
|
|
// NIP-11 Relay Information
|
|
if (config_value_changed(old_config, new_config, "relay_description") ||
|
|
config_value_changed(old_config, new_config, "relay_contact") ||
|
|
config_value_changed(old_config, new_config, "relay_software") ||
|
|
config_value_changed(old_config, new_config, "relay_version") ||
|
|
config_value_changed(old_config, new_config, "max_message_length") ||
|
|
config_value_changed(old_config, new_config, "max_event_tags") ||
|
|
config_value_changed(old_config, new_config, "max_content_length")) {
|
|
log_info("Relay information changed - reinitializing relay info");
|
|
init_relay_info();
|
|
handlers_applied++;
|
|
}
|
|
|
|
// Log configuration changes for audit
|
|
if (handlers_applied > 0) {
|
|
char audit_msg[512];
|
|
snprintf(audit_msg, sizeof(audit_msg),
|
|
"Configuration updated via kind 33334 event - %d system components reinitialized",
|
|
handlers_applied);
|
|
log_success(audit_msg);
|
|
} else {
|
|
log_info("No runtime configuration changes detected");
|
|
}
|
|
|
|
return handlers_applied;
|
|
}
|
|
|
|
int apply_configuration_from_event(const cJSON* event) {
|
|
if (!event) {
|
|
log_error("Invalid event for configuration application");
|
|
return -1;
|
|
}
|
|
|
|
log_info("Applying configuration from event...");
|
|
|
|
// Store previous config for comparison
|
|
cJSON* old_config = g_current_config;
|
|
|
|
// Update cached configuration
|
|
g_current_config = cJSON_Duplicate(event, 1);
|
|
|
|
// Extract admin pubkey if not already set
|
|
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
|
if (pubkey_obj && strlen(g_config_manager.admin_pubkey) == 0) {
|
|
strncpy(g_config_manager.admin_pubkey, cJSON_GetStringValue(pubkey_obj),
|
|
sizeof(g_config_manager.admin_pubkey) - 1);
|
|
}
|
|
|
|
// Apply runtime configuration changes
|
|
int handlers_applied = apply_runtime_config_handlers(old_config, g_current_config);
|
|
|
|
// Clean up old config
|
|
if (old_config) {
|
|
cJSON_Delete(old_config);
|
|
}
|
|
|
|
char success_msg[256];
|
|
snprintf(success_msg, sizeof(success_msg),
|
|
"Configuration applied from event (%d handlers executed)", handlers_applied);
|
|
log_success(success_msg);
|
|
return 0;
|
|
}
|
|
|
|
// ================================
|
|
// REAL-TIME EVENT HANDLER (called from main.c)
|
|
// ================================
|
|
|
|
// Handle kind 33334 configuration events received via WebSocket
|
|
int handle_configuration_event(cJSON* event, char* error_message, size_t error_size) {
|
|
if (!event) {
|
|
snprintf(error_message, error_size, "invalid: null configuration event");
|
|
return -1;
|
|
}
|
|
|
|
log_info("Handling configuration event from WebSocket");
|
|
|
|
// Use existing process_configuration_event function
|
|
if (process_configuration_event(event) == 0) {
|
|
// Success
|
|
error_message[0] = '\0'; // Empty error message indicates success
|
|
return 0;
|
|
} else {
|
|
// Failed to process
|
|
snprintf(error_message, error_size, "error: failed to process configuration event");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// ================================
|
|
// RETRY INITIAL CONFIG EVENT STORAGE
|
|
// ================================
|
|
|
|
int retry_store_initial_config_event(void) {
|
|
if (!g_pending_config_event) {
|
|
// No pending event to store
|
|
return 0;
|
|
}
|
|
|
|
log_info("Retrying storage of initial configuration event...");
|
|
|
|
// Try to store the cached configuration event
|
|
if (store_config_event_in_database(g_pending_config_event) == 0) {
|
|
log_success("Initial configuration event stored successfully on retry");
|
|
|
|
// Clean up the pending event
|
|
cJSON_Delete(g_pending_config_event);
|
|
g_pending_config_event = NULL;
|
|
return 0;
|
|
} else {
|
|
log_error("Failed to store initial configuration event on retry");
|
|
return -1;
|
|
}
|
|
} |