v0.3.1 - Implement database location and extension changes
- Change database extension from .nrdb to .db for standard SQLite convention - Modify make_and_restart_relay.sh to run executable from build/ directory - Database files now created in build/ directory alongside executable - Enhanced --preserve-database flag with backup/restore functionality - Updated source code references in config.c and main.c - Port auto-increment functionality remains fully functional
This commit is contained in:
393
src/config.c
393
src/config.c
@@ -44,9 +44,9 @@ char** find_existing_nrdb_files(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Count .nrdb files
|
||||
// Count .db files
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strstr(entry->d_name, ".nrdb") != NULL) {
|
||||
if (strstr(entry->d_name, ".db") != NULL) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ char** find_existing_nrdb_files(void) {
|
||||
// Store filenames
|
||||
int i = 0;
|
||||
while ((entry = readdir(dir)) != NULL && i < count) {
|
||||
if (strstr(entry->d_name, ".nrdb") != NULL) {
|
||||
if (strstr(entry->d_name, ".db") != NULL) {
|
||||
files[i] = malloc(strlen(entry->d_name) + 1);
|
||||
if (files[i]) {
|
||||
strcpy(files[i], entry->d_name);
|
||||
@@ -84,8 +84,8 @@ char** find_existing_nrdb_files(void) {
|
||||
char* extract_pubkey_from_filename(const char* filename) {
|
||||
if (!filename) return NULL;
|
||||
|
||||
// Find .nrdb extension
|
||||
const char* dot = strstr(filename, ".nrdb");
|
||||
// Find .db extension
|
||||
const char* dot = strstr(filename, ".db");
|
||||
if (!dot) return NULL;
|
||||
|
||||
// Calculate pubkey length
|
||||
@@ -107,10 +107,10 @@ char* get_database_name_from_relay_pubkey(const char* relay_pubkey) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* db_name = malloc(strlen(relay_pubkey) + 6); // +6 for ".nrdb\0"
|
||||
char* db_name = malloc(strlen(relay_pubkey) + 4); // +4 for ".db\0"
|
||||
if (!db_name) return NULL;
|
||||
|
||||
sprintf(db_name, "%s.nrdb", relay_pubkey);
|
||||
|
||||
sprintf(db_name, "%s.db", relay_pubkey);
|
||||
return db_name;
|
||||
}
|
||||
|
||||
@@ -642,6 +642,373 @@ int startup_existing_relay(const char* relay_pubkey) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ================================
|
||||
// CONFIGURATION FIELD VALIDATION
|
||||
// ================================
|
||||
|
||||
// Validation helper functions
|
||||
static int is_valid_port(const char* port_str) {
|
||||
if (!port_str) return 0;
|
||||
|
||||
char* endptr;
|
||||
long port = strtol(port_str, &endptr, 10);
|
||||
|
||||
// Must be valid number and in valid port range
|
||||
return (endptr != port_str && *endptr == '\0' && port >= 1 && port <= 65535);
|
||||
}
|
||||
|
||||
static int is_valid_boolean(const char* bool_str) {
|
||||
if (!bool_str) return 0;
|
||||
|
||||
return (strcasecmp(bool_str, "true") == 0 ||
|
||||
strcasecmp(bool_str, "false") == 0 ||
|
||||
strcasecmp(bool_str, "yes") == 0 ||
|
||||
strcasecmp(bool_str, "no") == 0 ||
|
||||
strcasecmp(bool_str, "1") == 0 ||
|
||||
strcasecmp(bool_str, "0") == 0);
|
||||
}
|
||||
|
||||
static int is_valid_positive_integer(const char* int_str) {
|
||||
if (!int_str) return 0;
|
||||
|
||||
char* endptr;
|
||||
long val = strtol(int_str, &endptr, 10);
|
||||
|
||||
return (endptr != int_str && *endptr == '\0' && val >= 0);
|
||||
}
|
||||
|
||||
static int is_valid_non_negative_integer(const char* int_str) {
|
||||
if (!int_str) return 0;
|
||||
|
||||
char* endptr;
|
||||
long val = strtol(int_str, &endptr, 10);
|
||||
|
||||
return (endptr != int_str && *endptr == '\0' && val >= 0);
|
||||
}
|
||||
|
||||
static int is_valid_string_length(const char* str, size_t max_length) {
|
||||
if (!str) return 1; // NULL strings are valid (use defaults)
|
||||
return strlen(str) <= max_length;
|
||||
}
|
||||
|
||||
static int is_valid_pow_mode(const char* mode_str) {
|
||||
if (!mode_str) return 0;
|
||||
|
||||
return (strcasecmp(mode_str, "basic") == 0 ||
|
||||
strcasecmp(mode_str, "strict") == 0 ||
|
||||
strcasecmp(mode_str, "disabled") == 0);
|
||||
}
|
||||
|
||||
static int is_valid_hex_key(const char* key_str) {
|
||||
if (!key_str) return 0;
|
||||
|
||||
// Must be exactly 64 hex characters
|
||||
if (strlen(key_str) != 64) return 0;
|
||||
|
||||
// Must contain only hex characters
|
||||
for (int i = 0; i < 64; i++) {
|
||||
char c = key_str[i];
|
||||
if (!((c >= '0' && c <= '9') ||
|
||||
(c >= 'a' && c <= 'f') ||
|
||||
(c >= 'A' && c <= 'F'))) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Main validation function for configuration fields
|
||||
static int validate_config_field(const char* key, const char* value, char* error_msg, size_t error_size) {
|
||||
if (!key || !value) {
|
||||
snprintf(error_msg, error_size, "key or value is NULL");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Port validation
|
||||
if (strcmp(key, "relay_port") == 0) {
|
||||
if (!is_valid_port(value)) {
|
||||
snprintf(error_msg, error_size, "invalid port number '%s' (must be 1-65535)", value);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Connection limits
|
||||
if (strcmp(key, "max_connections") == 0) {
|
||||
if (!is_valid_positive_integer(value)) {
|
||||
snprintf(error_msg, error_size, "invalid max_connections '%s' (must be positive integer)", value);
|
||||
return -1;
|
||||
}
|
||||
int val = atoi(value);
|
||||
if (val < 1 || val > 10000) {
|
||||
snprintf(error_msg, error_size, "max_connections '%s' out of range (1-10000)", value);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Boolean fields
|
||||
if (strcmp(key, "auth_enabled") == 0 ||
|
||||
strcmp(key, "nip40_expiration_enabled") == 0 ||
|
||||
strcmp(key, "nip40_expiration_strict") == 0 ||
|
||||
strcmp(key, "nip40_expiration_filter") == 0) {
|
||||
if (!is_valid_boolean(value)) {
|
||||
snprintf(error_msg, error_size, "invalid boolean value '%s' for %s", value, key);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// String length validation
|
||||
if (strcmp(key, "relay_description") == 0) {
|
||||
if (!is_valid_string_length(value, RELAY_DESCRIPTION_MAX_LENGTH)) {
|
||||
snprintf(error_msg, error_size, "relay_description too long (max %d characters)", RELAY_DESCRIPTION_MAX_LENGTH);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(key, "relay_contact") == 0) {
|
||||
if (!is_valid_string_length(value, RELAY_CONTACT_MAX_LENGTH)) {
|
||||
snprintf(error_msg, error_size, "relay_contact too long (max %d characters)", RELAY_CONTACT_MAX_LENGTH);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(key, "relay_software") == 0 ||
|
||||
strcmp(key, "relay_version") == 0) {
|
||||
if (!is_valid_string_length(value, 256)) {
|
||||
snprintf(error_msg, error_size, "%s too long (max 256 characters)", key);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// PoW difficulty validation
|
||||
if (strcmp(key, "pow_min_difficulty") == 0) {
|
||||
if (!is_valid_non_negative_integer(value)) {
|
||||
snprintf(error_msg, error_size, "invalid pow_min_difficulty '%s' (must be non-negative integer)", value);
|
||||
return -1;
|
||||
}
|
||||
int val = atoi(value);
|
||||
if (val > 32) { // 32 is practically impossible
|
||||
snprintf(error_msg, error_size, "pow_min_difficulty '%s' too high (max 32)", value);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// PoW mode validation
|
||||
if (strcmp(key, "pow_mode") == 0) {
|
||||
if (!is_valid_pow_mode(value)) {
|
||||
snprintf(error_msg, error_size, "invalid pow_mode '%s' (must be basic, strict, or disabled)", value);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Time-based validation
|
||||
if (strcmp(key, "nip40_expiration_grace_period") == 0) {
|
||||
if (!is_valid_non_negative_integer(value)) {
|
||||
snprintf(error_msg, error_size, "invalid grace period '%s' (must be non-negative integer)", value);
|
||||
return -1;
|
||||
}
|
||||
int val = atoi(value);
|
||||
if (val > 86400) { // Max 1 day
|
||||
snprintf(error_msg, error_size, "grace period '%s' too long (max 86400 seconds)", value);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Subscription limits
|
||||
if (strcmp(key, "max_subscriptions_per_client") == 0) {
|
||||
if (!is_valid_positive_integer(value)) {
|
||||
snprintf(error_msg, error_size, "invalid max_subscriptions_per_client '%s'", value);
|
||||
return -1;
|
||||
}
|
||||
int val = atoi(value);
|
||||
if (val < 1 || val > 1000) {
|
||||
snprintf(error_msg, error_size, "max_subscriptions_per_client '%s' out of range (1-1000)", value);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(key, "max_total_subscriptions") == 0) {
|
||||
if (!is_valid_positive_integer(value)) {
|
||||
snprintf(error_msg, error_size, "invalid max_total_subscriptions '%s'", value);
|
||||
return -1;
|
||||
}
|
||||
int val = atoi(value);
|
||||
if (val < 1 || val > 100000) {
|
||||
snprintf(error_msg, error_size, "max_total_subscriptions '%s' out of range (1-100000)", value);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(key, "max_filters_per_subscription") == 0) {
|
||||
if (!is_valid_positive_integer(value)) {
|
||||
snprintf(error_msg, error_size, "invalid max_filters_per_subscription '%s'", value);
|
||||
return -1;
|
||||
}
|
||||
int val = atoi(value);
|
||||
if (val < 1 || val > 100) {
|
||||
snprintf(error_msg, error_size, "max_filters_per_subscription '%s' out of range (1-100)", value);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Event limits
|
||||
if (strcmp(key, "max_event_tags") == 0) {
|
||||
if (!is_valid_positive_integer(value)) {
|
||||
snprintf(error_msg, error_size, "invalid max_event_tags '%s'", value);
|
||||
return -1;
|
||||
}
|
||||
int val = atoi(value);
|
||||
if (val < 1 || val > 1000) {
|
||||
snprintf(error_msg, error_size, "max_event_tags '%s' out of range (1-1000)", value);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(key, "max_content_length") == 0) {
|
||||
if (!is_valid_positive_integer(value)) {
|
||||
snprintf(error_msg, error_size, "invalid max_content_length '%s'", value);
|
||||
return -1;
|
||||
}
|
||||
int val = atoi(value);
|
||||
if (val < 100 || val > 1048576) { // 1MB max
|
||||
snprintf(error_msg, error_size, "max_content_length '%s' out of range (100-1048576)", value);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(key, "max_message_length") == 0) {
|
||||
if (!is_valid_positive_integer(value)) {
|
||||
snprintf(error_msg, error_size, "invalid max_message_length '%s'", value);
|
||||
return -1;
|
||||
}
|
||||
int val = atoi(value);
|
||||
if (val < 1024 || val > 1048576) { // 1KB to 1MB
|
||||
snprintf(error_msg, error_size, "max_message_length '%s' out of range (1024-1048576)", value);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Performance limits
|
||||
if (strcmp(key, "default_limit") == 0 || strcmp(key, "max_limit") == 0) {
|
||||
if (!is_valid_positive_integer(value)) {
|
||||
snprintf(error_msg, error_size, "invalid %s '%s'", key, value);
|
||||
return -1;
|
||||
}
|
||||
int val = atoi(value);
|
||||
if (val < 1 || val > 50000) {
|
||||
snprintf(error_msg, error_size, "%s '%s' out of range (1-50000)", key, value);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Key validation for relay keys
|
||||
if (strcmp(key, "relay_pubkey") == 0 || strcmp(key, "relay_privkey") == 0) {
|
||||
if (!is_valid_hex_key(value)) {
|
||||
snprintf(error_msg, error_size, "invalid %s format (must be 64-character hex)", key);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Special validation for d tag (relay identifier)
|
||||
if (strcmp(key, "d") == 0) {
|
||||
if (!is_valid_hex_key(value)) {
|
||||
snprintf(error_msg, error_size, "invalid relay identifier 'd' format (must be 64-character hex)");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Unknown field - log warning but allow
|
||||
log_warning("Unknown configuration field");
|
||||
printf(" Field: %s = %s\n", key, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Validate all fields in a configuration event
|
||||
static int validate_configuration_event_fields(const cJSON* event, char* error_msg, size_t error_size) {
|
||||
if (!event) {
|
||||
snprintf(error_msg, error_size, "null configuration event");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Validating configuration event fields...");
|
||||
|
||||
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||
if (!tags || !cJSON_IsArray(tags)) {
|
||||
snprintf(error_msg, error_size, "missing or invalid tags array");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int validated_fields = 0;
|
||||
int validation_errors = 0;
|
||||
char field_error[512];
|
||||
|
||||
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)) {
|
||||
|
||||
const char* key = cJSON_GetStringValue(tag_key);
|
||||
const char* value = cJSON_GetStringValue(tag_value);
|
||||
|
||||
if (validate_config_field(key, value, field_error, sizeof(field_error)) != 0) {
|
||||
// Safely truncate the error message if needed
|
||||
size_t prefix_len = strlen("field validation failed: ");
|
||||
size_t available_space = error_size > prefix_len ? error_size - prefix_len - 1 : 0;
|
||||
|
||||
if (available_space > 0) {
|
||||
snprintf(error_msg, error_size, "field validation failed: %.*s",
|
||||
(int)available_space, field_error);
|
||||
} else {
|
||||
strncpy(error_msg, "field validation failed", error_size - 1);
|
||||
error_msg[error_size - 1] = '\0';
|
||||
}
|
||||
|
||||
log_error("Configuration field validation failed");
|
||||
printf(" Field: %s = %s\n", key, value);
|
||||
printf(" Error: %s\n", field_error);
|
||||
validation_errors++;
|
||||
return -1; // Stop on first error
|
||||
} else {
|
||||
validated_fields++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (validation_errors > 0) {
|
||||
char summary[256];
|
||||
snprintf(summary, sizeof(summary), "%d configuration fields failed validation", validation_errors);
|
||||
log_error(summary);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char success_msg[256];
|
||||
snprintf(success_msg, sizeof(success_msg), "%d configuration fields validated successfully", validated_fields);
|
||||
log_success(success_msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int process_configuration_event(const cJSON* event) {
|
||||
if (!event) {
|
||||
log_error("Invalid configuration event");
|
||||
@@ -690,6 +1057,14 @@ int process_configuration_event(const cJSON* event) {
|
||||
|
||||
log_success("Configuration event structure and signature validated successfully");
|
||||
|
||||
// NEW: Validate configuration field values
|
||||
char validation_error[512];
|
||||
if (validate_configuration_event_fields(event, validation_error, sizeof(validation_error)) != 0) {
|
||||
log_error("Configuration field validation failed");
|
||||
printf(" Validation error: %s\n", validation_error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Store in database
|
||||
if (store_config_event_in_database(event) != 0) {
|
||||
log_error("Failed to store configuration event");
|
||||
@@ -702,7 +1077,7 @@ int process_configuration_event(const cJSON* event) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_success("Configuration event processed successfully");
|
||||
log_success("Configuration event processed successfully with field validation");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user