157 lines
5.9 KiB
C
157 lines
5.9 KiB
C
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include "debug.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)
|
|
};
|
|
|
|
|
|
// Initialize expiration configuration using configuration system
|
|
void init_expiration_config() {
|
|
|
|
// 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) {
|
|
DEBUG_WARN("Invalid grace period, using default of 300 seconds");
|
|
g_expiration_config.grace_period = 300;
|
|
}
|
|
|
|
}
|
|
|
|
// 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);
|
|
DEBUG_WARN(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);
|
|
DEBUG_WARN("Event rejected: expired timestamp");
|
|
return -1;
|
|
} else {
|
|
// In non-strict mode, allow expired events
|
|
}
|
|
}
|
|
|
|
return 0; // Success
|
|
} |