Fixing whitelist and blacklist functionality
This commit is contained in:
901
src/websockets.c
Normal file
901
src/websockets.c
Normal file
@@ -0,0 +1,901 @@
|
||||
// Define _GNU_SOURCE to ensure all POSIX features are available
|
||||
#define _GNU_SOURCE
|
||||
|
||||
// Includes
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
// Include libwebsockets after pthread.h to ensure pthread_rwlock_t is defined
|
||||
#include <libwebsockets.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
// Include nostr_core_lib for Nostr functionality
|
||||
#include "../nostr_core_lib/cjson/cJSON.h"
|
||||
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
||||
#include "../nostr_core_lib/nostr_core/nip013.h" // NIP-13: Proof of Work
|
||||
#include "config.h" // Configuration management system
|
||||
#include "sql_schema.h" // Embedded database schema
|
||||
#include "websockets.h" // WebSocket structures and constants
|
||||
#include "subscriptions.h" // Subscription structures and functions
|
||||
|
||||
// Forward declarations for logging functions
|
||||
void log_info(const char* message);
|
||||
void log_success(const char* message);
|
||||
void log_error(const char* message);
|
||||
void log_warning(const char* message);
|
||||
|
||||
// Forward declarations for configuration functions
|
||||
const char* get_config_value(const char* key);
|
||||
int get_config_int(const char* key, int default_value);
|
||||
int get_config_bool(const char* key, int default_value);
|
||||
// Forward declarations for NIP-42 authentication functions
|
||||
int is_nip42_auth_globally_required(void);
|
||||
int is_nip42_auth_required_for_kind(int kind);
|
||||
void send_nip42_auth_challenge(struct lws* wsi, struct per_session_data* pss);
|
||||
void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* pss, cJSON* auth_event);
|
||||
void handle_nip42_auth_challenge_response(struct lws* wsi, struct per_session_data* pss, const char* challenge);
|
||||
|
||||
// Forward declarations for NIP-11 relay information handling
|
||||
int handle_nip11_http_request(struct lws* wsi, const char* accept_header);
|
||||
|
||||
// Forward declarations for database functions
|
||||
int store_event(cJSON* event);
|
||||
|
||||
// Forward declarations for subscription management
|
||||
int broadcast_event_to_subscriptions(cJSON* event);
|
||||
int add_subscription_to_manager(struct subscription* sub);
|
||||
int remove_subscription_from_manager(const char* sub_id, struct lws* wsi);
|
||||
|
||||
// Forward declarations for event handling
|
||||
int handle_event_message(cJSON* event, char* error_message, size_t error_size);
|
||||
int nostr_validate_unified_request(const char* json_string, size_t json_length);
|
||||
|
||||
// Forward declarations for admin event processing
|
||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
// Forward declarations for NIP-09 deletion request handling
|
||||
int handle_deletion_request(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
// Forward declarations for NIP-13 PoW handling
|
||||
int validate_event_pow(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
// Forward declarations for NIP-40 expiration handling
|
||||
int is_event_expired(cJSON* event, time_t current_time);
|
||||
|
||||
// Forward declarations for subscription handling
|
||||
int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, struct per_session_data *pss);
|
||||
|
||||
// Forward declarations for NOTICE message support
|
||||
void send_notice_message(struct lws* wsi, const char* message);
|
||||
|
||||
// Forward declarations for unified cache access
|
||||
extern unified_config_cache_t g_unified_cache;
|
||||
|
||||
// Forward declarations for global state
|
||||
extern sqlite3* g_db;
|
||||
extern int g_server_running;
|
||||
extern struct lws_context *ws_context;
|
||||
|
||||
// Global subscription manager
|
||||
struct subscription_manager g_subscription_manager;
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// WEBSOCKET PROTOCOL
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// WebSocket callback function for Nostr relay protocol
|
||||
static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reason,
|
||||
void *user, void *in, size_t len) {
|
||||
struct per_session_data *pss = (struct per_session_data *)user;
|
||||
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_HTTP:
|
||||
// Handle NIP-11 relay information requests (HTTP GET to root path)
|
||||
{
|
||||
char *requested_uri = (char *)in;
|
||||
log_info("HTTP request received");
|
||||
|
||||
// Check if this is a GET request to the root path
|
||||
if (strcmp(requested_uri, "/") == 0) {
|
||||
// Get Accept header
|
||||
char accept_header[256] = {0};
|
||||
int header_len = lws_hdr_copy(wsi, accept_header, sizeof(accept_header) - 1, WSI_TOKEN_HTTP_ACCEPT);
|
||||
|
||||
if (header_len > 0) {
|
||||
accept_header[header_len] = '\0';
|
||||
|
||||
// Handle NIP-11 request
|
||||
if (handle_nip11_http_request(wsi, accept_header) == 0) {
|
||||
return 0; // Successfully handled
|
||||
}
|
||||
} else {
|
||||
log_warning("HTTP request without Accept header");
|
||||
}
|
||||
|
||||
// Return 404 for other requests
|
||||
lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Return 404 for non-root paths
|
||||
lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
case LWS_CALLBACK_HTTP_WRITEABLE:
|
||||
// Handle NIP-11 HTTP body transmission with proper buffer management
|
||||
{
|
||||
struct nip11_session_data* session_data = (struct nip11_session_data*)lws_wsi_user(wsi);
|
||||
if (session_data && session_data->headers_sent && !session_data->body_sent) {
|
||||
// Allocate buffer for JSON body transmission
|
||||
unsigned char *json_buf = malloc(LWS_PRE + session_data->json_length);
|
||||
if (!json_buf) {
|
||||
log_error("Failed to allocate buffer for NIP-11 body transmission");
|
||||
// Clean up session data
|
||||
free(session_data->json_buffer);
|
||||
free(session_data);
|
||||
lws_set_wsi_user(wsi, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Copy JSON data to buffer
|
||||
memcpy(json_buf + LWS_PRE, session_data->json_buffer, session_data->json_length);
|
||||
|
||||
// Write JSON body
|
||||
int write_result = lws_write(wsi, json_buf + LWS_PRE, session_data->json_length, LWS_WRITE_HTTP);
|
||||
|
||||
// Free the transmission buffer immediately (it's been copied by libwebsockets)
|
||||
free(json_buf);
|
||||
|
||||
if (write_result < 0) {
|
||||
log_error("Failed to write NIP-11 JSON body");
|
||||
// Clean up session data
|
||||
free(session_data->json_buffer);
|
||||
free(session_data);
|
||||
lws_set_wsi_user(wsi, NULL);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Mark body as sent and clean up session data
|
||||
session_data->body_sent = 1;
|
||||
free(session_data->json_buffer);
|
||||
free(session_data);
|
||||
lws_set_wsi_user(wsi, NULL);
|
||||
|
||||
log_success("NIP-11 relay information served successfully");
|
||||
return 0; // Close connection after successful transmission
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_ESTABLISHED:
|
||||
log_info("WebSocket connection established");
|
||||
memset(pss, 0, sizeof(*pss));
|
||||
pthread_mutex_init(&pss->session_lock, NULL);
|
||||
|
||||
// Get real client IP address
|
||||
char client_ip[CLIENT_IP_MAX_LENGTH];
|
||||
lws_get_peer_simple(wsi, client_ip, sizeof(client_ip));
|
||||
|
||||
// Ensure client_ip is null-terminated and copy safely
|
||||
client_ip[CLIENT_IP_MAX_LENGTH - 1] = '\0';
|
||||
size_t ip_len = strlen(client_ip);
|
||||
size_t copy_len = (ip_len < CLIENT_IP_MAX_LENGTH - 1) ? ip_len : CLIENT_IP_MAX_LENGTH - 1;
|
||||
memcpy(pss->client_ip, client_ip, copy_len);
|
||||
pss->client_ip[copy_len] = '\0';
|
||||
|
||||
// Initialize NIP-42 authentication state
|
||||
pss->authenticated = 0;
|
||||
pss->nip42_auth_required_events = get_config_bool("nip42_auth_required_events", 0);
|
||||
pss->nip42_auth_required_subscriptions = get_config_bool("nip42_auth_required_subscriptions", 0);
|
||||
pss->auth_challenge_sent = 0;
|
||||
memset(pss->authenticated_pubkey, 0, sizeof(pss->authenticated_pubkey));
|
||||
memset(pss->active_challenge, 0, sizeof(pss->active_challenge));
|
||||
pss->challenge_created = 0;
|
||||
pss->challenge_expires = 0;
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_RECEIVE:
|
||||
if (len > 0) {
|
||||
char *message = malloc(len + 1);
|
||||
if (message) {
|
||||
memcpy(message, in, len);
|
||||
message[len] = '\0';
|
||||
|
||||
// Parse JSON message (this is the normal program flow)
|
||||
cJSON* json = cJSON_Parse(message);
|
||||
if (json && cJSON_IsArray(json)) {
|
||||
// Log the complete parsed JSON message once
|
||||
char* complete_message = cJSON_Print(json);
|
||||
if (complete_message) {
|
||||
char debug_msg[2048];
|
||||
snprintf(debug_msg, sizeof(debug_msg),
|
||||
"Received complete WebSocket message: %s", complete_message);
|
||||
log_info(debug_msg);
|
||||
free(complete_message);
|
||||
}
|
||||
// Get message type
|
||||
cJSON* type = cJSON_GetArrayItem(json, 0);
|
||||
if (type && cJSON_IsString(type)) {
|
||||
const char* msg_type = cJSON_GetStringValue(type);
|
||||
|
||||
if (strcmp(msg_type, "EVENT") == 0) {
|
||||
// Extract event for kind-specific NIP-42 authentication check
|
||||
cJSON* event_obj = cJSON_GetArrayItem(json, 1);
|
||||
if (event_obj && cJSON_IsObject(event_obj)) {
|
||||
// Extract event kind for kind-specific NIP-42 authentication check
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event_obj, "kind");
|
||||
int event_kind = kind_obj && cJSON_IsNumber(kind_obj) ? (int)cJSON_GetNumberValue(kind_obj) : -1;
|
||||
|
||||
// Extract pubkey and event ID for debugging
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event_obj, "pubkey");
|
||||
cJSON* id_obj = cJSON_GetObjectItem(event_obj, "id");
|
||||
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : "unknown";
|
||||
const char* event_id = id_obj ? cJSON_GetStringValue(id_obj) : "unknown";
|
||||
|
||||
char debug_event_msg[512];
|
||||
snprintf(debug_event_msg, sizeof(debug_event_msg),
|
||||
"DEBUG EVENT: Processing kind %d event from pubkey %.16s... ID %.16s...",
|
||||
event_kind, event_pubkey, event_id);
|
||||
log_info(debug_event_msg);
|
||||
|
||||
// Check if NIP-42 authentication is required for this event kind or globally
|
||||
int auth_required = is_nip42_auth_globally_required() || is_nip42_auth_required_for_kind(event_kind);
|
||||
|
||||
char debug_auth_msg[256];
|
||||
snprintf(debug_auth_msg, sizeof(debug_auth_msg),
|
||||
"DEBUG AUTH: auth_required=%d, pss->authenticated=%d, event_kind=%d",
|
||||
auth_required, pss ? pss->authenticated : -1, event_kind);
|
||||
log_info(debug_auth_msg);
|
||||
|
||||
if (pss && auth_required && !pss->authenticated) {
|
||||
if (!pss->auth_challenge_sent) {
|
||||
log_info("DEBUG AUTH: Sending NIP-42 authentication challenge");
|
||||
send_nip42_auth_challenge(wsi, pss);
|
||||
} else {
|
||||
char auth_msg[256];
|
||||
if (event_kind == 4 || event_kind == 14) {
|
||||
snprintf(auth_msg, sizeof(auth_msg),
|
||||
"NIP-42 authentication required for direct message events (kind %d)", event_kind);
|
||||
} else {
|
||||
snprintf(auth_msg, sizeof(auth_msg),
|
||||
"NIP-42 authentication required for event kind %d", event_kind);
|
||||
}
|
||||
send_notice_message(wsi, auth_msg);
|
||||
log_warning("Event rejected: NIP-42 authentication required for kind");
|
||||
char debug_msg[128];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Auth required for kind %d", event_kind);
|
||||
log_info(debug_msg);
|
||||
}
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check blacklist/whitelist rules regardless of NIP-42 auth settings
|
||||
// Blacklist should always be enforced
|
||||
if (event_pubkey) {
|
||||
// Forward declaration for auth rules checking function
|
||||
extern int check_database_auth_rules(const char *pubkey, const char *operation, const char *resource_hash);
|
||||
|
||||
int auth_rules_result = check_database_auth_rules(event_pubkey, "event", NULL);
|
||||
if (auth_rules_result != 0) { // 0 = NOSTR_SUCCESS, non-zero = blocked
|
||||
char auth_rules_msg[256];
|
||||
if (auth_rules_result == -101) { // NOSTR_ERROR_AUTH_REQUIRED
|
||||
snprintf(auth_rules_msg, sizeof(auth_rules_msg),
|
||||
"blocked: pubkey not authorized (blacklist/whitelist violation)");
|
||||
} else {
|
||||
snprintf(auth_rules_msg, sizeof(auth_rules_msg),
|
||||
"blocked: authorization check failed (error %d)", auth_rules_result);
|
||||
}
|
||||
send_notice_message(wsi, auth_rules_msg);
|
||||
log_warning("Event rejected: blacklist/whitelist violation");
|
||||
|
||||
// Send OK response with false status
|
||||
cJSON* response = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(response, cJSON_CreateString("OK"));
|
||||
cJSON_AddItemToArray(response, cJSON_CreateString(event_id));
|
||||
cJSON_AddItemToArray(response, cJSON_CreateBool(0)); // false = rejected
|
||||
cJSON_AddItemToArray(response, cJSON_CreateString(auth_rules_msg));
|
||||
|
||||
char *response_str = cJSON_Print(response);
|
||||
if (response_str) {
|
||||
size_t response_len = strlen(response_str);
|
||||
unsigned char *buf = malloc(LWS_PRE + response_len);
|
||||
if (buf) {
|
||||
memcpy(buf + LWS_PRE, response_str, response_len);
|
||||
lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
|
||||
free(buf);
|
||||
}
|
||||
free(response_str);
|
||||
}
|
||||
cJSON_Delete(response);
|
||||
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle EVENT message
|
||||
cJSON* event = cJSON_GetArrayItem(json, 1);
|
||||
if (event && cJSON_IsObject(event)) {
|
||||
// Extract event JSON string for unified validator
|
||||
char *event_json_str = cJSON_Print(event);
|
||||
if (!event_json_str) {
|
||||
log_error("Failed to serialize event JSON for validation");
|
||||
cJSON* error_response = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(error_response, cJSON_CreateString("OK"));
|
||||
cJSON_AddItemToArray(error_response, cJSON_CreateString("unknown"));
|
||||
cJSON_AddItemToArray(error_response, cJSON_CreateBool(0));
|
||||
cJSON_AddItemToArray(error_response, cJSON_CreateString("error: failed to process event"));
|
||||
|
||||
char *error_str = cJSON_Print(error_response);
|
||||
if (error_str) {
|
||||
size_t error_len = strlen(error_str);
|
||||
unsigned char *buf = malloc(LWS_PRE + error_len);
|
||||
if (buf) {
|
||||
memcpy(buf + LWS_PRE, error_str, error_len);
|
||||
lws_write(wsi, buf + LWS_PRE, error_len, LWS_WRITE_TEXT);
|
||||
free(buf);
|
||||
}
|
||||
free(error_str);
|
||||
}
|
||||
cJSON_Delete(error_response);
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_info("DEBUG VALIDATION: Starting unified validator");
|
||||
|
||||
// Call unified validator with JSON string
|
||||
size_t event_json_len = strlen(event_json_str);
|
||||
int validation_result = nostr_validate_unified_request(event_json_str, event_json_len);
|
||||
|
||||
// Map validation result to old result format (0 = success, -1 = failure)
|
||||
int result = (validation_result == NOSTR_SUCCESS) ? 0 : -1;
|
||||
|
||||
char debug_validation_msg[256];
|
||||
snprintf(debug_validation_msg, sizeof(debug_validation_msg),
|
||||
"DEBUG VALIDATION: validation_result=%d, result=%d", validation_result, result);
|
||||
log_info(debug_validation_msg);
|
||||
|
||||
// Generate error message based on validation result
|
||||
char error_message[512] = {0};
|
||||
if (result != 0) {
|
||||
switch (validation_result) {
|
||||
case NOSTR_ERROR_INVALID_INPUT:
|
||||
strncpy(error_message, "invalid: malformed event structure", sizeof(error_message) - 1);
|
||||
break;
|
||||
case NOSTR_ERROR_EVENT_INVALID_SIGNATURE:
|
||||
strncpy(error_message, "invalid: signature verification failed", sizeof(error_message) - 1);
|
||||
break;
|
||||
case NOSTR_ERROR_EVENT_INVALID_ID:
|
||||
strncpy(error_message, "invalid: event id verification failed", sizeof(error_message) - 1);
|
||||
break;
|
||||
case NOSTR_ERROR_EVENT_INVALID_PUBKEY:
|
||||
strncpy(error_message, "invalid: invalid pubkey format", sizeof(error_message) - 1);
|
||||
break;
|
||||
case -103: // NOSTR_ERROR_EVENT_EXPIRED
|
||||
strncpy(error_message, "rejected: event expired", sizeof(error_message) - 1);
|
||||
break;
|
||||
case -102: // NOSTR_ERROR_NIP42_DISABLED
|
||||
strncpy(error_message, "auth-required: NIP-42 authentication required", sizeof(error_message) - 1);
|
||||
break;
|
||||
case -101: // NOSTR_ERROR_AUTH_REQUIRED
|
||||
strncpy(error_message, "blocked: pubkey not authorized", sizeof(error_message) - 1);
|
||||
break;
|
||||
default:
|
||||
strncpy(error_message, "error: validation failed", sizeof(error_message) - 1);
|
||||
break;
|
||||
}
|
||||
char debug_error_msg[256];
|
||||
snprintf(debug_error_msg, sizeof(debug_error_msg),
|
||||
"DEBUG VALIDATION ERROR: %s", error_message);
|
||||
log_warning(debug_error_msg);
|
||||
} else {
|
||||
log_info("DEBUG VALIDATION: Event validated successfully using unified validator");
|
||||
}
|
||||
|
||||
// Cleanup event JSON string
|
||||
free(event_json_str);
|
||||
|
||||
// Check for admin events (kind 23456) and intercept them
|
||||
if (result == 0) {
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
if (kind_obj && cJSON_IsNumber(kind_obj)) {
|
||||
int event_kind = (int)cJSON_GetNumberValue(kind_obj);
|
||||
|
||||
log_info("DEBUG ADMIN: Checking if admin event processing is needed");
|
||||
|
||||
// Log reception of Kind 23456 events
|
||||
if (event_kind == 23456) {
|
||||
char* event_json_debug = cJSON_Print(event);
|
||||
char debug_received_msg[1024];
|
||||
snprintf(debug_received_msg, sizeof(debug_received_msg),
|
||||
"RECEIVED Kind %d event: %s", event_kind,
|
||||
event_json_debug ? event_json_debug : "Failed to serialize");
|
||||
log_info(debug_received_msg);
|
||||
|
||||
if (event_json_debug) {
|
||||
free(event_json_debug);
|
||||
}
|
||||
}
|
||||
|
||||
if (event_kind == 23456) {
|
||||
// Enhanced admin event security - check authorization first
|
||||
log_info("DEBUG ADMIN: Admin event detected, checking authorization");
|
||||
|
||||
char auth_error[512] = {0};
|
||||
int auth_result = is_authorized_admin_event(event, auth_error, sizeof(auth_error));
|
||||
|
||||
if (auth_result != 0) {
|
||||
// Authorization failed - log and reject
|
||||
log_warning("DEBUG ADMIN: Admin event authorization failed");
|
||||
result = -1;
|
||||
size_t error_len = strlen(auth_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
memcpy(error_message, auth_error, copy_len);
|
||||
error_message[copy_len] = '\0';
|
||||
|
||||
char debug_auth_error_msg[600];
|
||||
snprintf(debug_auth_error_msg, sizeof(debug_auth_error_msg),
|
||||
"DEBUG ADMIN AUTH ERROR: %.400s", auth_error);
|
||||
log_warning(debug_auth_error_msg);
|
||||
} else {
|
||||
// Authorization successful - process through admin API
|
||||
log_info("DEBUG ADMIN: Admin event authorized, processing through admin API");
|
||||
|
||||
char admin_error[512] = {0};
|
||||
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
|
||||
|
||||
char debug_admin_msg[256];
|
||||
snprintf(debug_admin_msg, sizeof(debug_admin_msg),
|
||||
"DEBUG ADMIN: process_admin_event_in_config returned %d", admin_result);
|
||||
log_info(debug_admin_msg);
|
||||
|
||||
// Log results for Kind 23456 events
|
||||
if (event_kind == 23456) {
|
||||
if (admin_result == 0) {
|
||||
char success_result_msg[256];
|
||||
snprintf(success_result_msg, sizeof(success_result_msg),
|
||||
"SUCCESS: Kind %d event processed successfully", event_kind);
|
||||
log_success(success_result_msg);
|
||||
} else {
|
||||
char error_result_msg[512];
|
||||
snprintf(error_result_msg, sizeof(error_result_msg),
|
||||
"ERROR: Kind %d event processing failed: %s", event_kind, admin_error);
|
||||
log_error(error_result_msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (admin_result != 0) {
|
||||
log_error("DEBUG ADMIN: Failed to process admin event through admin API");
|
||||
result = -1;
|
||||
size_t error_len = strlen(admin_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
memcpy(error_message, admin_error, copy_len);
|
||||
error_message[copy_len] = '\0';
|
||||
|
||||
char debug_admin_error_msg[600];
|
||||
snprintf(debug_admin_error_msg, sizeof(debug_admin_error_msg),
|
||||
"DEBUG ADMIN ERROR: %.400s", admin_error);
|
||||
log_error(debug_admin_error_msg);
|
||||
} else {
|
||||
log_success("DEBUG ADMIN: Admin event processed successfully through admin API");
|
||||
// Admin events are processed by the admin API, not broadcast to subscriptions
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Regular event - store in database and broadcast
|
||||
log_info("DEBUG STORAGE: Regular event - storing in database");
|
||||
if (store_event(event) != 0) {
|
||||
log_error("DEBUG STORAGE: Failed to store event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
log_info("DEBUG STORAGE: Event stored successfully in database");
|
||||
// Broadcast event to matching persistent subscriptions
|
||||
int broadcast_count = broadcast_event_to_subscriptions(event);
|
||||
char debug_broadcast_msg[128];
|
||||
snprintf(debug_broadcast_msg, sizeof(debug_broadcast_msg),
|
||||
"DEBUG BROADCAST: Event broadcast to %d subscriptions", broadcast_count);
|
||||
log_info(debug_broadcast_msg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Event without valid kind - try normal storage
|
||||
log_warning("DEBUG STORAGE: Event without valid kind - trying normal storage");
|
||||
if (store_event(event) != 0) {
|
||||
log_error("DEBUG STORAGE: Failed to store event without kind in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
log_info("DEBUG STORAGE: Event without kind stored successfully in database");
|
||||
broadcast_event_to_subscriptions(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send OK response
|
||||
cJSON* event_id = cJSON_GetObjectItem(event, "id");
|
||||
if (event_id && cJSON_IsString(event_id)) {
|
||||
cJSON* response = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(response, cJSON_CreateString("OK"));
|
||||
cJSON_AddItemToArray(response, cJSON_CreateString(cJSON_GetStringValue(event_id)));
|
||||
cJSON_AddItemToArray(response, cJSON_CreateBool(result == 0));
|
||||
cJSON_AddItemToArray(response, cJSON_CreateString(strlen(error_message) > 0 ? error_message : ""));
|
||||
|
||||
// TODO: REPLACE - Remove wasteful cJSON_Print conversion
|
||||
char *response_str = cJSON_Print(response);
|
||||
if (response_str) {
|
||||
char debug_response_msg[512];
|
||||
snprintf(debug_response_msg, sizeof(debug_response_msg),
|
||||
"DEBUG RESPONSE: Sending OK response: %s", response_str);
|
||||
log_info(debug_response_msg);
|
||||
|
||||
size_t response_len = strlen(response_str);
|
||||
unsigned char *buf = malloc(LWS_PRE + response_len);
|
||||
if (buf) {
|
||||
memcpy(buf + LWS_PRE, response_str, response_len);
|
||||
int write_result = lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
|
||||
|
||||
char debug_write_msg[128];
|
||||
snprintf(debug_write_msg, sizeof(debug_write_msg),
|
||||
"DEBUG RESPONSE: lws_write returned %d", write_result);
|
||||
log_info(debug_write_msg);
|
||||
|
||||
free(buf);
|
||||
}
|
||||
free(response_str);
|
||||
}
|
||||
cJSON_Delete(response);
|
||||
}
|
||||
}
|
||||
} else if (strcmp(msg_type, "REQ") == 0) {
|
||||
// Check NIP-42 authentication for REQ subscriptions if required
|
||||
if (pss && pss->nip42_auth_required_subscriptions && !pss->authenticated) {
|
||||
if (!pss->auth_challenge_sent) {
|
||||
send_nip42_auth_challenge(wsi, pss);
|
||||
} else {
|
||||
send_notice_message(wsi, "NIP-42 authentication required for subscriptions");
|
||||
log_warning("REQ rejected: NIP-42 authentication required");
|
||||
}
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle REQ message
|
||||
cJSON* sub_id = cJSON_GetArrayItem(json, 1);
|
||||
|
||||
if (sub_id && cJSON_IsString(sub_id)) {
|
||||
const char* subscription_id = cJSON_GetStringValue(sub_id);
|
||||
|
||||
// Create array of filter objects from position 2 onwards
|
||||
cJSON* filters = cJSON_CreateArray();
|
||||
int json_size = cJSON_GetArraySize(json);
|
||||
for (int i = 2; i < json_size; i++) {
|
||||
cJSON* filter = cJSON_GetArrayItem(json, i);
|
||||
if (filter) {
|
||||
cJSON_AddItemToArray(filters, cJSON_Duplicate(filter, 1));
|
||||
}
|
||||
}
|
||||
|
||||
handle_req_message(subscription_id, filters, wsi, pss);
|
||||
|
||||
// Clean up the filters array we created
|
||||
cJSON_Delete(filters);
|
||||
|
||||
// Send EOSE (End of Stored Events)
|
||||
cJSON* eose_response = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(eose_response, cJSON_CreateString("EOSE"));
|
||||
cJSON_AddItemToArray(eose_response, cJSON_CreateString(subscription_id));
|
||||
|
||||
char *eose_str = cJSON_Print(eose_response);
|
||||
if (eose_str) {
|
||||
size_t eose_len = strlen(eose_str);
|
||||
unsigned char *buf = malloc(LWS_PRE + eose_len);
|
||||
if (buf) {
|
||||
memcpy(buf + LWS_PRE, eose_str, eose_len);
|
||||
lws_write(wsi, buf + LWS_PRE, eose_len, LWS_WRITE_TEXT);
|
||||
free(buf);
|
||||
}
|
||||
free(eose_str);
|
||||
}
|
||||
cJSON_Delete(eose_response);
|
||||
}
|
||||
} else if (strcmp(msg_type, "CLOSE") == 0) {
|
||||
// Handle CLOSE message
|
||||
cJSON* sub_id = cJSON_GetArrayItem(json, 1);
|
||||
if (sub_id && cJSON_IsString(sub_id)) {
|
||||
const char* subscription_id = cJSON_GetStringValue(sub_id);
|
||||
|
||||
// Remove from global manager
|
||||
remove_subscription_from_manager(subscription_id, wsi);
|
||||
|
||||
// Remove from session list if present
|
||||
if (pss) {
|
||||
pthread_mutex_lock(&pss->session_lock);
|
||||
|
||||
struct subscription** current = &pss->subscriptions;
|
||||
while (*current) {
|
||||
if (strcmp((*current)->id, subscription_id) == 0) {
|
||||
struct subscription* to_remove = *current;
|
||||
*current = to_remove->session_next;
|
||||
pss->subscription_count--;
|
||||
break;
|
||||
}
|
||||
current = &((*current)->session_next);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&pss->session_lock);
|
||||
}
|
||||
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Closed subscription: %s", subscription_id);
|
||||
log_info(debug_msg);
|
||||
}
|
||||
} else if (strcmp(msg_type, "AUTH") == 0) {
|
||||
// Handle NIP-42 AUTH message
|
||||
if (cJSON_GetArraySize(json) >= 2) {
|
||||
cJSON* auth_payload = cJSON_GetArrayItem(json, 1);
|
||||
|
||||
if (cJSON_IsString(auth_payload)) {
|
||||
// AUTH challenge response: ["AUTH", <challenge>] (unusual)
|
||||
handle_nip42_auth_challenge_response(wsi, pss, cJSON_GetStringValue(auth_payload));
|
||||
} else if (cJSON_IsObject(auth_payload)) {
|
||||
// AUTH signed event: ["AUTH", <event>] (standard NIP-42)
|
||||
handle_nip42_auth_signed_event(wsi, pss, auth_payload);
|
||||
} else {
|
||||
send_notice_message(wsi, "Invalid AUTH message format");
|
||||
log_warning("Received AUTH message with invalid payload type");
|
||||
}
|
||||
} else {
|
||||
send_notice_message(wsi, "AUTH message requires payload");
|
||||
log_warning("Received AUTH message without payload");
|
||||
}
|
||||
} else {
|
||||
// Unknown message type
|
||||
char unknown_msg[128];
|
||||
snprintf(unknown_msg, sizeof(unknown_msg), "Unknown message type: %.32s", msg_type);
|
||||
log_warning(unknown_msg);
|
||||
send_notice_message(wsi, "Unknown message type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (json) cJSON_Delete(json);
|
||||
free(message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_CLOSED:
|
||||
log_info("WebSocket connection closed");
|
||||
|
||||
// Clean up session subscriptions
|
||||
if (pss) {
|
||||
pthread_mutex_lock(&pss->session_lock);
|
||||
|
||||
struct subscription* sub = pss->subscriptions;
|
||||
while (sub) {
|
||||
struct subscription* next = sub->session_next;
|
||||
remove_subscription_from_manager(sub->id, wsi);
|
||||
sub = next;
|
||||
}
|
||||
|
||||
pss->subscriptions = NULL;
|
||||
pss->subscription_count = 0;
|
||||
|
||||
pthread_mutex_unlock(&pss->session_lock);
|
||||
pthread_mutex_destroy(&pss->session_lock);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// WebSocket protocol definition
|
||||
static struct lws_protocols protocols[] = {
|
||||
{
|
||||
"nostr-relay-protocol",
|
||||
nostr_relay_callback,
|
||||
sizeof(struct per_session_data),
|
||||
4096, // rx buffer size
|
||||
0, NULL, 0
|
||||
},
|
||||
{ NULL, NULL, 0, 0, 0, NULL, 0 } // terminator
|
||||
};
|
||||
|
||||
// Check if a port is available for binding
|
||||
int check_port_available(int port) {
|
||||
int sockfd;
|
||||
struct sockaddr_in addr;
|
||||
int result;
|
||||
int reuse = 1;
|
||||
|
||||
// Create a socket
|
||||
sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sockfd < 0) {
|
||||
return 0; // Cannot create socket, assume port unavailable
|
||||
}
|
||||
|
||||
// Set SO_REUSEADDR to allow binding to ports in TIME_WAIT state
|
||||
// This matches libwebsockets behavior and prevents false unavailability
|
||||
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
|
||||
close(sockfd);
|
||||
return 0; // Failed to set socket option
|
||||
}
|
||||
|
||||
// Set up the address structure
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
// Try to bind to the port
|
||||
result = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
|
||||
|
||||
// Close the socket
|
||||
close(sockfd);
|
||||
|
||||
// Return 1 if bind succeeded (port available), 0 if failed (port in use)
|
||||
return (result == 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
// Start libwebsockets-based WebSocket Nostr relay server
|
||||
int start_websocket_relay(int port_override, int strict_port) {
|
||||
struct lws_context_creation_info info;
|
||||
|
||||
log_info("Starting libwebsockets-based Nostr relay server...");
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
// Use port override if provided, otherwise use configuration
|
||||
int configured_port = (port_override > 0) ? port_override : get_config_int("relay_port", DEFAULT_PORT);
|
||||
int actual_port = configured_port;
|
||||
int port_attempts = 0;
|
||||
const int max_port_attempts = 10; // Increased from 5 to 10
|
||||
|
||||
// Minimal libwebsockets configuration
|
||||
info.protocols = protocols;
|
||||
info.gid = -1;
|
||||
info.uid = -1;
|
||||
info.options = LWS_SERVER_OPTION_VALIDATE_UTF8;
|
||||
|
||||
// Remove interface restrictions - let system choose
|
||||
// info.vhost_name = NULL;
|
||||
// info.iface = NULL;
|
||||
|
||||
// Increase max connections for relay usage
|
||||
info.max_http_header_pool = 16;
|
||||
info.timeout_secs = 10;
|
||||
|
||||
// Max payload size for Nostr events
|
||||
info.max_http_header_data = 4096;
|
||||
|
||||
// Find an available port with pre-checking (or fail immediately in strict mode)
|
||||
while (port_attempts < (strict_port ? 1 : max_port_attempts)) {
|
||||
char attempt_msg[256];
|
||||
snprintf(attempt_msg, sizeof(attempt_msg), "Checking port availability: %d", actual_port);
|
||||
log_info(attempt_msg);
|
||||
|
||||
// Pre-check if port is available
|
||||
if (!check_port_available(actual_port)) {
|
||||
port_attempts++;
|
||||
if (strict_port) {
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg),
|
||||
"Strict port mode: port %d is not available", actual_port);
|
||||
log_error(error_msg);
|
||||
return -1;
|
||||
} else if (port_attempts < max_port_attempts) {
|
||||
char retry_msg[256];
|
||||
snprintf(retry_msg, sizeof(retry_msg), "Port %d is in use, trying port %d (attempt %d/%d)",
|
||||
actual_port, actual_port + 1, port_attempts + 1, max_port_attempts);
|
||||
log_warning(retry_msg);
|
||||
actual_port++;
|
||||
continue;
|
||||
} else {
|
||||
char error_msg[512];
|
||||
snprintf(error_msg, sizeof(error_msg),
|
||||
"Failed to find available port after %d attempts (tried ports %d-%d)",
|
||||
max_port_attempts, configured_port, actual_port);
|
||||
log_error(error_msg);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Port appears available, try creating libwebsockets context
|
||||
info.port = actual_port;
|
||||
|
||||
char binding_msg[256];
|
||||
snprintf(binding_msg, sizeof(binding_msg), "Attempting to bind libwebsockets to port %d", actual_port);
|
||||
log_info(binding_msg);
|
||||
|
||||
ws_context = lws_create_context(&info);
|
||||
if (ws_context) {
|
||||
// Success! Port binding worked
|
||||
break;
|
||||
}
|
||||
|
||||
// libwebsockets failed even though port check passed
|
||||
// This could be due to timing or different socket options
|
||||
int errno_saved = errno;
|
||||
char lws_error_msg[256];
|
||||
snprintf(lws_error_msg, sizeof(lws_error_msg),
|
||||
"libwebsockets failed to bind to port %d (errno: %d)", actual_port, errno_saved);
|
||||
log_warning(lws_error_msg);
|
||||
|
||||
port_attempts++;
|
||||
if (strict_port) {
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg),
|
||||
"Strict port mode: failed to bind to port %d", actual_port);
|
||||
log_error(error_msg);
|
||||
break;
|
||||
} else if (port_attempts < max_port_attempts) {
|
||||
actual_port++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we get here, we've exhausted attempts
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ws_context) {
|
||||
char error_msg[512];
|
||||
snprintf(error_msg, sizeof(error_msg),
|
||||
"Failed to create libwebsockets context after %d attempts. Last attempted port: %d",
|
||||
port_attempts, actual_port);
|
||||
log_error(error_msg);
|
||||
perror("libwebsockets creation error");
|
||||
return -1;
|
||||
}
|
||||
|
||||
char startup_msg[256];
|
||||
if (actual_port != configured_port) {
|
||||
snprintf(startup_msg, sizeof(startup_msg),
|
||||
"WebSocket relay started on ws://127.0.0.1:%d (configured port %d was unavailable)",
|
||||
actual_port, configured_port);
|
||||
log_warning(startup_msg);
|
||||
} else {
|
||||
snprintf(startup_msg, sizeof(startup_msg), "WebSocket relay started on ws://127.0.0.1:%d", actual_port);
|
||||
}
|
||||
log_success(startup_msg);
|
||||
|
||||
// Main event loop with proper signal handling
|
||||
while (g_server_running) {
|
||||
int result = lws_service(ws_context, 1000);
|
||||
|
||||
if (result < 0) {
|
||||
log_error("libwebsockets service error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
log_info("Shutting down WebSocket server...");
|
||||
lws_context_destroy(ws_context);
|
||||
ws_context = NULL;
|
||||
|
||||
log_success("WebSocket relay shut down cleanly");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user