Fixing whitelist and blacklist functionality
This commit is contained in:
173
src/nip040.c
Normal file
173
src/nip040.c
Normal file
@@ -0,0 +1,173 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
// Include nostr_core_lib for cJSON
|
||||
#include "../nostr_core_lib/cjson/cJSON.h"
|
||||
|
||||
// Configuration management system
|
||||
#include "config.h"
|
||||
|
||||
// NIP-40 Expiration configuration structure
|
||||
struct expiration_config {
|
||||
int enabled; // 0 = disabled, 1 = enabled
|
||||
int strict_mode; // 1 = reject expired events on submission
|
||||
int filter_responses; // 1 = filter expired events from responses
|
||||
int delete_expired; // 1 = delete expired events from DB (future feature)
|
||||
long grace_period; // Grace period in seconds for clock skew
|
||||
};
|
||||
|
||||
// Global expiration configuration instance
|
||||
struct expiration_config g_expiration_config = {
|
||||
.enabled = 1, // Enable expiration handling by default
|
||||
.strict_mode = 1, // Reject expired events on submission by default
|
||||
.filter_responses = 1, // Filter expired events from responses by default
|
||||
.delete_expired = 0, // Don't delete by default (keep for audit)
|
||||
.grace_period = 1 // 1 second grace period for testing (was 300)
|
||||
};
|
||||
|
||||
// Forward declarations for logging functions
|
||||
void log_info(const char* message);
|
||||
void log_warning(const char* message);
|
||||
|
||||
// Initialize expiration configuration using configuration system
|
||||
void init_expiration_config() {
|
||||
log_info("Initializing NIP-40 Expiration Timestamp configuration");
|
||||
|
||||
// Get all config values first (without holding mutex to avoid deadlock)
|
||||
int expiration_enabled = get_config_bool("expiration_enabled", 1);
|
||||
int expiration_strict = get_config_bool("expiration_strict", 1);
|
||||
int expiration_filter = get_config_bool("expiration_filter", 1);
|
||||
int expiration_delete = get_config_bool("expiration_delete", 0);
|
||||
long expiration_grace_period = get_config_int("expiration_grace_period", 1);
|
||||
|
||||
// Load expiration settings from configuration system
|
||||
g_expiration_config.enabled = expiration_enabled;
|
||||
g_expiration_config.strict_mode = expiration_strict;
|
||||
g_expiration_config.filter_responses = expiration_filter;
|
||||
g_expiration_config.delete_expired = expiration_delete;
|
||||
g_expiration_config.grace_period = expiration_grace_period;
|
||||
|
||||
// Validate grace period bounds
|
||||
if (g_expiration_config.grace_period < 0 || g_expiration_config.grace_period > 86400) {
|
||||
log_warning("Invalid grace period, using default of 300 seconds");
|
||||
g_expiration_config.grace_period = 300;
|
||||
}
|
||||
|
||||
// Log final configuration
|
||||
char config_msg[512];
|
||||
snprintf(config_msg, sizeof(config_msg),
|
||||
"Expiration Configuration: enabled=%s, strict_mode=%s, filter_responses=%s, grace_period=%ld seconds",
|
||||
g_expiration_config.enabled ? "true" : "false",
|
||||
g_expiration_config.strict_mode ? "true" : "false",
|
||||
g_expiration_config.filter_responses ? "true" : "false",
|
||||
g_expiration_config.grace_period);
|
||||
log_info(config_msg);
|
||||
}
|
||||
|
||||
// Extract expiration timestamp from event tags
|
||||
long extract_expiration_timestamp(cJSON* tags) {
|
||||
if (!tags || !cJSON_IsArray(tags)) {
|
||||
return 0; // No expiration
|
||||
}
|
||||
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags) {
|
||||
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
|
||||
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
|
||||
|
||||
if (cJSON_IsString(tag_name) && cJSON_IsString(tag_value)) {
|
||||
const char* name = cJSON_GetStringValue(tag_name);
|
||||
const char* value = cJSON_GetStringValue(tag_value);
|
||||
|
||||
if (name && value && strcmp(name, "expiration") == 0) {
|
||||
// Validate that the string contains only digits (and optional leading whitespace)
|
||||
const char* p = value;
|
||||
|
||||
// Skip leading whitespace
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
|
||||
// Check if we have at least one digit
|
||||
if (*p == '\0') {
|
||||
continue; // Empty or whitespace-only string, ignore this tag
|
||||
}
|
||||
|
||||
// Validate that all remaining characters are digits
|
||||
const char* digit_start = p;
|
||||
while (*p >= '0' && *p <= '9') p++;
|
||||
|
||||
// If we didn't consume the entire string or found no digits, it's malformed
|
||||
if (*p != '\0' || p == digit_start) {
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg),
|
||||
"Ignoring malformed expiration tag value: '%.32s'", value);
|
||||
log_warning(debug_msg);
|
||||
continue; // Ignore malformed expiration tag
|
||||
}
|
||||
|
||||
long expiration_ts = atol(value);
|
||||
if (expiration_ts > 0) {
|
||||
return expiration_ts;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // No valid expiration tag found
|
||||
}
|
||||
|
||||
// Check if event is currently expired
|
||||
int is_event_expired(cJSON* event, time_t current_time) {
|
||||
if (!event) {
|
||||
return 0; // Invalid event, not expired
|
||||
}
|
||||
|
||||
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||
long expiration_ts = extract_expiration_timestamp(tags);
|
||||
|
||||
if (expiration_ts == 0) {
|
||||
return 0; // No expiration timestamp, not expired
|
||||
}
|
||||
|
||||
// Check if current time exceeds expiration + grace period
|
||||
return (current_time > (expiration_ts + g_expiration_config.grace_period));
|
||||
}
|
||||
|
||||
// Validate event expiration according to NIP-40
|
||||
int validate_event_expiration(cJSON* event, char* error_message, size_t error_size) {
|
||||
if (!g_expiration_config.enabled) {
|
||||
return 0; // Expiration validation disabled
|
||||
}
|
||||
|
||||
if (!event) {
|
||||
snprintf(error_message, error_size, "expiration: null event");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if event is expired
|
||||
time_t current_time = time(NULL);
|
||||
if (is_event_expired(event, current_time)) {
|
||||
if (g_expiration_config.strict_mode) {
|
||||
cJSON* tags = cJSON_GetObjectItem(event, "tags");
|
||||
long expiration_ts = extract_expiration_timestamp(tags);
|
||||
|
||||
snprintf(error_message, error_size,
|
||||
"invalid: event expired (expiration=%ld, current=%ld, grace=%ld)",
|
||||
expiration_ts, (long)current_time, g_expiration_config.grace_period);
|
||||
log_warning("Event rejected: expired timestamp");
|
||||
return -1;
|
||||
} else {
|
||||
// In non-strict mode, log but allow expired events
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg),
|
||||
"Accepting expired event (strict_mode disabled)");
|
||||
log_info(debug_msg);
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
Reference in New Issue
Block a user