Compare commits

..

2 Commits

Author SHA1 Message Date
Your Name
7acc0bdd90 v1.2.3 - Handle rate limiting properly on kind 99999 request 2026-02-09 07:49:43 -04:00
Your Name
c4ef71d673 v1.2.2 - Add + to allowed subscription characters 2026-02-07 13:32:00 -04:00
5 changed files with 128 additions and 49 deletions

View File

@@ -1,19 +1,12 @@
#!/bin/bash #!/bin/bash
# Restart the service
sudo systemctl stop c-relay.service
# Copy the binary to the deployment location # Copy the binary to the deployment location
cp build/c_relay_x86 ~/Storage/c_relay/crelay cp build/c_relay_static_x86_64 ~/Storage/c_relay/crelay
# Copy the service file to systemd (use the main service file)
sudo cp systemd/c-relay.service /etc/systemd/system/c-relay-local.service
# Reload systemd daemon to pick up the new service
sudo systemctl daemon-reload
# Enable the service (if not already enabled)
sudo systemctl enable c-relay-local.service
# Restart the service # Restart the service
sudo systemctl restart c-relay-local.service sudo systemctl restart c-relay.service
# Show service status # Show service status
sudo systemctl status c-relay-local.service --no-pager -l sudo systemctl status c-relay.service --no-pager -l

View File

@@ -2554,20 +2554,46 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
return -1; return -1;
} }
DEBUG_LOG("=== NIP-44 DECRYPTION DEBUG (Level 5) ===");
DEBUG_LOG("Relay privkey (first 16 chars): %.16s...", relay_privkey);
DEBUG_LOG("Sender pubkey: %s", sender_pubkey);
DEBUG_LOG("Content length: %zu", strlen(content));
DEBUG_LOG("Content (first 100 chars): %.100s%s", content, strlen(content) > 100 ? "..." : "");
// Perform NIP-44 decryption (relay as recipient, admin as sender) // Perform NIP-44 decryption (relay as recipient, admin as sender)
DEBUG_LOG("Calling nostr_nip44_decrypt...");
DEBUG_LOG(" relay_privkey_bytes (first 8 bytes hex): %02x%02x%02x%02x%02x%02x%02x%02x...",
relay_privkey_bytes[0], relay_privkey_bytes[1], relay_privkey_bytes[2], relay_privkey_bytes[3],
relay_privkey_bytes[4], relay_privkey_bytes[5], relay_privkey_bytes[6], relay_privkey_bytes[7]);
DEBUG_LOG(" sender_pubkey_bytes (first 8 bytes hex): %02x%02x%02x%02x%02x%02x%02x%02x...",
sender_pubkey_bytes[0], sender_pubkey_bytes[1], sender_pubkey_bytes[2], sender_pubkey_bytes[3],
sender_pubkey_bytes[4], sender_pubkey_bytes[5], sender_pubkey_bytes[6], sender_pubkey_bytes[7]);
char decrypted_text[16384]; // Buffer for decrypted content (16KB) char decrypted_text[16384]; // Buffer for decrypted content (16KB)
int decrypt_result = nostr_nip44_decrypt(relay_privkey_bytes, sender_pubkey_bytes, content, decrypted_text, sizeof(decrypted_text)); int decrypt_result = nostr_nip44_decrypt(relay_privkey_bytes, sender_pubkey_bytes, content, decrypted_text, sizeof(decrypted_text));
DEBUG_LOG("nostr_nip44_decrypt returned: %d (NOSTR_SUCCESS=%d)", decrypt_result, NOSTR_SUCCESS);
// Clean up private key immediately after use // Clean up private key immediately after use
memset(relay_privkey_bytes, 0, 32); memset(relay_privkey_bytes, 0, 32);
free(relay_privkey); free(relay_privkey);
if (decrypt_result != NOSTR_SUCCESS) { if (decrypt_result != NOSTR_SUCCESS) {
DEBUG_ERROR("error: NIP-44 decryption failed"); DEBUG_ERROR("error: NIP-44 decryption failed with code %d", decrypt_result);
snprintf(error_message, error_size, "error: NIP-44 decryption failed"); DEBUG_ERROR(" This means the encrypted content cannot be decrypted with the provided keys");
DEBUG_ERROR(" Possible causes:");
DEBUG_ERROR(" 1. Content was encrypted for a different relay pubkey");
DEBUG_ERROR(" 2. Content format is incompatible (wrong NIP-44 version)");
DEBUG_ERROR(" 3. Content is corrupted or malformed");
snprintf(error_message, error_size, "error: NIP-44 decryption failed (code: %d)", decrypt_result);
return -1; return -1;
} }
DEBUG_LOG("✓ NIP-44 decryption successful!");
DEBUG_LOG("Decrypted text length: %zu", strlen(decrypted_text));
DEBUG_LOG("Decrypted text (first 200 chars): %.200s%s", decrypted_text, strlen(decrypted_text) > 200 ? "..." : "");
DEBUG_LOG("=== END NIP-44 DECRYPTION DEBUG ===");
// Parse decrypted content as command array directly (NOT as NIP-17 inner event) // Parse decrypted content as command array directly (NOT as NIP-17 inner event)
// Kind 23456 events contain direct command arrays: ["command_name", arg1, arg2, ...] // Kind 23456 events contain direct command arrays: ["command_name", arg1, arg2, ...]
decrypted_content = cJSON_Parse(decrypted_text); decrypted_content = cJSON_Parse(decrypted_text);

View File

@@ -844,18 +844,74 @@ cJSON* retrieve_event(const char* event_id) {
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
/**
* Check if filters contain only kind 99999 (NDK ping)
* Returns 1 if all filters only request kind 99999, 0 otherwise
*/
static int is_only_kind_99999_request(cJSON* filters) {
if (!filters || !cJSON_IsArray(filters)) {
return 0;
}
int filter_count = cJSON_GetArraySize(filters);
if (filter_count == 0) {
return 0;
}
for (int i = 0; i < filter_count; i++) {
cJSON* filter = cJSON_GetArrayItem(filters, i);
if (!filter || !cJSON_IsObject(filter)) {
return 0;
}
cJSON* kinds = cJSON_GetObjectItem(filter, "kinds");
if (!kinds || !cJSON_IsArray(kinds)) {
// Filter has no kinds or kinds is not an array - not a pure 99999 request
return 0;
}
int kinds_count = cJSON_GetArraySize(kinds);
if (kinds_count == 0) {
return 0;
}
for (int j = 0; j < kinds_count; j++) {
cJSON* kind_item = cJSON_GetArrayItem(kinds, j);
if (!cJSON_IsNumber(kind_item)) {
return 0;
}
int kind_val = (int)cJSON_GetNumberValue(kind_item);
if (kind_val != 99999) {
return 0;
}
}
}
return 1; // All filters only contain kind 99999
}
int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, struct per_session_data *pss) { int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, struct per_session_data *pss) {
if (!cJSON_IsArray(filters)) { if (!cJSON_IsArray(filters)) {
DEBUG_ERROR("REQ filters is not an array"); DEBUG_ERROR("REQ filters is not an array");
return 0; return 0;
} }
// Check if this is a kind 99999 (NDK ping) request - these should never count toward rate limiting
int is_ndk_ping = is_only_kind_99999_request(filters);
// EARLY SUBSCRIPTION LIMIT CHECK - Check limits BEFORE any processing // EARLY SUBSCRIPTION LIMIT CHECK - Check limits BEFORE any processing
if (pss) { if (pss) {
time_t current_time = time(NULL); time_t current_time = time(NULL);
// Check if client is currently rate limited due to excessive failed attempts // Check if client is currently rate limited due to excessive failed attempts
if (pss->rate_limit_until > current_time) { if (pss->rate_limit_until > current_time) {
// NDK ping (kind 99999) should not be blocked by rate limiting at all
if (is_ndk_ping) {
// Allow the request through - it will be handled normally and get a proper response
// The subscription will be created but no events will match (which is correct)
DEBUG_TRACE("Allowing kind 99999 NDK ping through despite rate limit");
// Fall through to normal processing (skip rate limit block)
} else {
char rate_limit_msg[256]; char rate_limit_msg[256];
int remaining_seconds = (int)(pss->rate_limit_until - current_time); int remaining_seconds = (int)(pss->rate_limit_until - current_time);
snprintf(rate_limit_msg, sizeof(rate_limit_msg), snprintf(rate_limit_msg, sizeof(rate_limit_msg),
@@ -881,21 +937,24 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
} }
cJSON_Delete(closed_msg); cJSON_Delete(closed_msg);
// Update rate limiting counters // Do NOT increment rate limiting counters here - the client is already rate limited.
pss->failed_subscription_attempts++; // Incrementing counters while already blocked would extend the punishment indefinitely,
pss->last_failed_attempt = current_time; // especially for benign requests like NDK's kind 99999 pings.
return 0; return 0;
} }
}
// Check session subscription limits // Check session subscription limits
if (pss->subscription_count >= g_subscription_manager.max_subscriptions_per_client) { if (pss->subscription_count >= g_subscription_manager.max_subscriptions_per_client) {
DEBUG_ERROR("Maximum subscriptions per client exceeded"); DEBUG_ERROR("Maximum subscriptions per client exceeded");
// Update rate limiting counters for failed attempt // Update rate limiting counters for failed attempt (but not for NDK ping)
if (!is_ndk_ping) {
pss->failed_subscription_attempts++; pss->failed_subscription_attempts++;
pss->last_failed_attempt = current_time; pss->last_failed_attempt = current_time;
pss->consecutive_failures++; pss->consecutive_failures++;
}
// Implement progressive backoff: 1s, 5s, 30s, 300s (5min) based on consecutive failures // Implement progressive backoff: 1s, 5s, 30s, 300s (5min) based on consecutive failures
int backoff_seconds = 1; int backoff_seconds = 1;
@@ -1010,7 +1069,8 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
cJSON_Delete(closed_msg); cJSON_Delete(closed_msg);
// Update rate limiting counters for failed attempt (global limit reached) // Update rate limiting counters for failed attempt (global limit reached)
if (pss) { // Do not count NDK ping (kind 99999) toward rate limiting
if (pss && !is_ndk_ping) {
time_t current_time = time(NULL); time_t current_time = time(NULL);
pss->failed_subscription_attempts++; pss->failed_subscription_attempts++;
pss->last_failed_attempt = current_time; pss->last_failed_attempt = current_time;

View File

@@ -13,8 +13,8 @@
// Using CRELAY_ prefix to avoid conflicts with nostr_core_lib VERSION macros // Using CRELAY_ prefix to avoid conflicts with nostr_core_lib VERSION macros
#define CRELAY_VERSION_MAJOR 1 #define CRELAY_VERSION_MAJOR 1
#define CRELAY_VERSION_MINOR 2 #define CRELAY_VERSION_MINOR 2
#define CRELAY_VERSION_PATCH 1 #define CRELAY_VERSION_PATCH 3
#define CRELAY_VERSION "v1.2.1" #define CRELAY_VERSION "v1.2.3"
// Relay metadata (authoritative source for NIP-11 information) // Relay metadata (authoritative source for NIP-11 information)
#define RELAY_NAME "C-Relay" #define RELAY_NAME "C-Relay"

View File

@@ -265,11 +265,11 @@ int validate_subscription_id(const char* sub_id) {
return 0; // Empty or too long return 0; // Empty or too long
} }
// Check for valid characters (alphanumeric, underscore, hyphen, colon, comma) // Check for valid characters (alphanumeric, underscore, hyphen, colon, comma, plus)
for (size_t i = 0; i < len; i++) { for (size_t i = 0; i < len; i++) {
char c = sub_id[i]; char c = sub_id[i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '_' || c == '-' || c == ':' || c == ',')) { (c >= '0' && c <= '9') || c == '_' || c == '-' || c == ':' || c == ',' || c == '+')) {
return 0; // Invalid character return 0; // Invalid character
} }
} }