674 lines
24 KiB
C
674 lines
24 KiB
C
// Admin event handler for Kind 23458/23459 admin commands
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include "ginxsom.h"
|
|
|
|
// Forward declarations for nostr_core_lib functions
|
|
int nostr_hex_to_bytes(const char* hex, unsigned char* bytes, size_t bytes_len);
|
|
int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
|
|
const unsigned char* sender_public_key,
|
|
const char* encrypted_data,
|
|
char* output,
|
|
size_t output_size);
|
|
int nostr_nip44_encrypt(const unsigned char* sender_private_key,
|
|
const unsigned char* recipient_public_key,
|
|
const char* plaintext,
|
|
char* output,
|
|
size_t output_size);
|
|
cJSON* nostr_create_and_sign_event(int kind, const char* content, cJSON* tags,
|
|
const unsigned char* private_key, time_t created_at);
|
|
|
|
// Use global database path from main.c
|
|
extern char g_db_path[];
|
|
|
|
// Forward declarations
|
|
static int get_server_privkey(unsigned char* privkey_bytes);
|
|
static int get_server_pubkey(char* pubkey_hex, size_t size);
|
|
static int handle_config_query_command(cJSON* response_data);
|
|
static int handle_query_view_command(cJSON* command_array, cJSON* response_data);
|
|
static int send_admin_response_event(const char* admin_pubkey, const char* request_id,
|
|
cJSON* response_data);
|
|
static cJSON* parse_authorization_header(void);
|
|
static int process_admin_event(cJSON* event);
|
|
|
|
/**
|
|
* Handle Kind 23458 admin command event
|
|
* Supports two delivery methods:
|
|
* 1. POST body with JSON event
|
|
* 2. Authorization header with Nostr event
|
|
*/
|
|
void handle_admin_event_request(void) {
|
|
cJSON* event = NULL;
|
|
int should_free_event = 1;
|
|
|
|
// First, try to get event from Authorization header
|
|
event = parse_authorization_header();
|
|
|
|
// If not in header, try POST body
|
|
if (!event) {
|
|
const char* content_length_str = getenv("CONTENT_LENGTH");
|
|
if (!content_length_str) {
|
|
printf("Status: 400 Bad Request\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Event required in POST body or Authorization header\"}\n");
|
|
return;
|
|
}
|
|
|
|
long content_length = atol(content_length_str);
|
|
if (content_length <= 0 || content_length > 65536) {
|
|
printf("Status: 400 Bad Request\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Invalid content length\"}\n");
|
|
return;
|
|
}
|
|
|
|
char* json_body = malloc(content_length + 1);
|
|
if (!json_body) {
|
|
printf("Status: 500 Internal Server Error\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Memory allocation failed\"}\n");
|
|
return;
|
|
}
|
|
|
|
size_t bytes_read = fread(json_body, 1, content_length, stdin);
|
|
if (bytes_read != (size_t)content_length) {
|
|
free(json_body);
|
|
printf("Status: 400 Bad Request\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Failed to read complete request body\"}\n");
|
|
return;
|
|
}
|
|
json_body[content_length] = '\0';
|
|
|
|
// Parse event JSON
|
|
event = cJSON_Parse(json_body);
|
|
|
|
// Debug: Log the received JSON
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: Received POST body: %s", json_body);
|
|
|
|
free(json_body);
|
|
|
|
if (!event) {
|
|
app_log(LOG_ERROR, "ADMIN_EVENT: Failed to parse JSON");
|
|
printf("Status: 400 Bad Request\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Invalid JSON\"}\n");
|
|
return;
|
|
}
|
|
|
|
// Debug: Log parsed event
|
|
char* event_str = cJSON_Print(event);
|
|
if (event_str) {
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: Parsed event: %s", event_str);
|
|
free(event_str);
|
|
}
|
|
}
|
|
|
|
// Process the event (handles validation, decryption, command execution, response)
|
|
int result = process_admin_event(event);
|
|
|
|
// Clean up
|
|
if (should_free_event && event) {
|
|
cJSON_Delete(event);
|
|
}
|
|
|
|
(void)result; // Result already handled by process_admin_event
|
|
}
|
|
|
|
/**
|
|
* Parse Kind 23458 event from Authorization header
|
|
* Format: Authorization: Nostr <base64-encoded-event-json>
|
|
* Returns: cJSON event object or NULL if not present/invalid
|
|
*/
|
|
static cJSON* parse_authorization_header(void) {
|
|
const char* auth_header = getenv("HTTP_AUTHORIZATION");
|
|
if (!auth_header) {
|
|
return NULL;
|
|
}
|
|
|
|
// Check for "Nostr " prefix (case-insensitive)
|
|
if (strncasecmp(auth_header, "Nostr ", 6) != 0) {
|
|
return NULL;
|
|
}
|
|
|
|
// Skip "Nostr " prefix
|
|
const char* base64_event = auth_header + 6;
|
|
|
|
// Decode base64 (simple implementation - in production use proper base64 decoder)
|
|
// For now, assume the event is JSON directly (not base64 encoded)
|
|
// This matches the pattern from c-relay's admin interface
|
|
cJSON* event = cJSON_Parse(base64_event);
|
|
|
|
return event;
|
|
}
|
|
|
|
/**
|
|
* Process a Kind 23458 admin event (from POST body or Authorization header)
|
|
* Returns: 0 on success, -1 on error (error response already sent)
|
|
*/
|
|
static int process_admin_event(cJSON* event) {
|
|
if (!event) {
|
|
printf("Status: 400 Bad Request\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Invalid event\"}\n");
|
|
return -1;
|
|
}
|
|
|
|
// Verify it's Kind 23458
|
|
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
|
if (!kind_obj || !cJSON_IsNumber(kind_obj) ||
|
|
(int)cJSON_GetNumberValue(kind_obj) != 23458) {
|
|
printf("Status: 400 Bad Request\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Event must be Kind 23458\"}\n");
|
|
return -1;
|
|
}
|
|
|
|
// Get event ID for response correlation
|
|
cJSON* id_obj = cJSON_GetObjectItem(event, "id");
|
|
if (!id_obj || !cJSON_IsString(id_obj)) {
|
|
printf("Status: 400 Bad Request\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Event missing id\"}\n");
|
|
return -1;
|
|
}
|
|
const char* request_id = cJSON_GetStringValue(id_obj);
|
|
|
|
// Get admin pubkey from event
|
|
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
|
if (!pubkey_obj || !cJSON_IsString(pubkey_obj)) {
|
|
printf("Status: 400 Bad Request\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Event missing pubkey\"}\n");
|
|
return -1;
|
|
}
|
|
const char* admin_pubkey = cJSON_GetStringValue(pubkey_obj);
|
|
|
|
// Verify admin pubkey
|
|
sqlite3* db;
|
|
int rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
printf("Status: 500 Internal Server Error\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Database error\"}\n");
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_stmt* stmt;
|
|
const char* sql = "SELECT value FROM config WHERE key = 'admin_pubkey'";
|
|
int is_admin = 0;
|
|
|
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
|
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
const char* db_admin_pubkey = (const char*)sqlite3_column_text(stmt, 0);
|
|
if (db_admin_pubkey && strcmp(admin_pubkey, db_admin_pubkey) == 0) {
|
|
is_admin = 1;
|
|
}
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
sqlite3_close(db);
|
|
|
|
if (!is_admin) {
|
|
printf("Status: 403 Forbidden\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Not authorized as admin\"}\n");
|
|
return -1;
|
|
}
|
|
|
|
// Get encrypted content
|
|
cJSON* content_obj = cJSON_GetObjectItem(event, "content");
|
|
if (!content_obj || !cJSON_IsString(content_obj)) {
|
|
printf("Status: 400 Bad Request\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Event missing content\"}\n");
|
|
return -1;
|
|
}
|
|
const char* encrypted_content = cJSON_GetStringValue(content_obj);
|
|
|
|
// Get server private key for decryption
|
|
unsigned char server_privkey[32];
|
|
if (get_server_privkey(server_privkey) != 0) {
|
|
printf("Status: 500 Internal Server Error\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Failed to get server private key\"}\n");
|
|
return -1;
|
|
}
|
|
|
|
// Convert admin pubkey to bytes
|
|
unsigned char admin_pubkey_bytes[32];
|
|
if (nostr_hex_to_bytes(admin_pubkey, admin_pubkey_bytes, 32) != 0) {
|
|
printf("Status: 400 Bad Request\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Invalid admin pubkey format\"}\n");
|
|
return -1;
|
|
}
|
|
|
|
// Decrypt content using NIP-44 (or use plaintext for testing)
|
|
char decrypted_content[8192];
|
|
const char* content_to_parse = encrypted_content;
|
|
|
|
// Check if content is already plaintext JSON (starts with '[')
|
|
if (encrypted_content[0] != '[') {
|
|
// Content is encrypted, decrypt it
|
|
int decrypt_result = nostr_nip44_decrypt(
|
|
server_privkey,
|
|
admin_pubkey_bytes,
|
|
encrypted_content,
|
|
decrypted_content,
|
|
sizeof(decrypted_content)
|
|
);
|
|
|
|
if (decrypt_result != 0) {
|
|
app_log(LOG_ERROR, "ADMIN_EVENT: Decryption failed with result: %d", decrypt_result);
|
|
app_log(LOG_ERROR, "ADMIN_EVENT: Encrypted content: %s", encrypted_content);
|
|
printf("Status: 400 Bad Request\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Failed to decrypt content\"}\n");
|
|
return -1;
|
|
}
|
|
content_to_parse = decrypted_content;
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: Decrypted content: %s", decrypted_content);
|
|
} else {
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: Using plaintext content (starts with '['): %s", encrypted_content);
|
|
}
|
|
|
|
// Parse command array (either decrypted or plaintext)
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: Parsing command array from: %s", content_to_parse);
|
|
cJSON* command_array = cJSON_Parse(content_to_parse);
|
|
if (!command_array || !cJSON_IsArray(command_array)) {
|
|
printf("Status: 400 Bad Request\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Decrypted content is not a valid command array\"}\n");
|
|
return -1;
|
|
}
|
|
|
|
// Get command type
|
|
cJSON* command_type = cJSON_GetArrayItem(command_array, 0);
|
|
if (!command_type || !cJSON_IsString(command_type)) {
|
|
cJSON_Delete(command_array);
|
|
printf("Status: 400 Bad Request\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Invalid command format\"}\n");
|
|
return -1;
|
|
}
|
|
|
|
const char* cmd = cJSON_GetStringValue(command_type);
|
|
|
|
// Create response data object
|
|
cJSON* response_data = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(response_data, "query_type", cmd);
|
|
cJSON_AddNumberToObject(response_data, "timestamp", (double)time(NULL));
|
|
|
|
// Handle command
|
|
int result = -1;
|
|
if (strcmp(cmd, "config_query") == 0) {
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: Handling config_query command");
|
|
result = handle_config_query_command(response_data);
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: config_query result: %d", result);
|
|
} else if (strcmp(cmd, "query_view") == 0) {
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: Handling query_view command");
|
|
result = handle_query_view_command(command_array, response_data);
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: query_view result: %d", result);
|
|
} else {
|
|
app_log(LOG_WARN, "ADMIN_EVENT: Unknown command: %s", cmd);
|
|
cJSON_AddStringToObject(response_data, "status", "error");
|
|
cJSON_AddStringToObject(response_data, "error", "Unknown command");
|
|
result = -1;
|
|
}
|
|
|
|
cJSON_Delete(command_array);
|
|
|
|
if (result == 0) {
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: Sending Kind 23459 response");
|
|
// Send Kind 23459 response
|
|
int send_result = send_admin_response_event(admin_pubkey, request_id, response_data);
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: Response sent with result: %d", send_result);
|
|
return send_result;
|
|
} else {
|
|
app_log(LOG_ERROR, "ADMIN_EVENT: Command processing failed");
|
|
cJSON_Delete(response_data);
|
|
printf("Status: 500 Internal Server Error\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Command processing failed\"}\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get server private key from database (stored in blossom_seckey table)
|
|
*/
|
|
static int get_server_privkey(unsigned char* privkey_bytes) {
|
|
sqlite3* db;
|
|
int rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_stmt* stmt;
|
|
const char* sql = "SELECT seckey FROM blossom_seckey LIMIT 1";
|
|
int result = -1;
|
|
|
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
|
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
const char* privkey_hex = (const char*)sqlite3_column_text(stmt, 0);
|
|
if (privkey_hex && nostr_hex_to_bytes(privkey_hex, privkey_bytes, 32) == 0) {
|
|
result = 0;
|
|
}
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
sqlite3_close(db);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get server public key from database (stored in config table as blossom_pubkey)
|
|
*/
|
|
static int get_server_pubkey(char* pubkey_hex, size_t size) {
|
|
sqlite3* db;
|
|
int rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_stmt* stmt;
|
|
const char* sql = "SELECT value FROM config WHERE key = 'blossom_pubkey'";
|
|
int result = -1;
|
|
|
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
|
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
const char* pubkey = (const char*)sqlite3_column_text(stmt, 0);
|
|
if (pubkey) {
|
|
strncpy(pubkey_hex, pubkey, size - 1);
|
|
pubkey_hex[size - 1] = '\0';
|
|
result = 0;
|
|
}
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
sqlite3_close(db);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Handle config_query command - returns all config values
|
|
*/
|
|
static int handle_config_query_command(cJSON* response_data) {
|
|
sqlite3* db;
|
|
int rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
cJSON_AddStringToObject(response_data, "status", "error");
|
|
cJSON_AddStringToObject(response_data, "error", "Database error");
|
|
return -1;
|
|
}
|
|
|
|
cJSON_AddStringToObject(response_data, "status", "success");
|
|
cJSON* data = cJSON_CreateObject();
|
|
|
|
// Query all config settings
|
|
sqlite3_stmt* stmt;
|
|
const char* sql = "SELECT key, value FROM config ORDER BY key";
|
|
|
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {
|
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
const char* key = (const char*)sqlite3_column_text(stmt, 0);
|
|
const char* value = (const char*)sqlite3_column_text(stmt, 1);
|
|
if (key && value) {
|
|
cJSON_AddStringToObject(data, key, value);
|
|
}
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
}
|
|
|
|
cJSON_AddItemToObject(response_data, "data", data);
|
|
sqlite3_close(db);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Handle query_view command - returns data from a specified database view
|
|
* Command format: ["query_view", "view_name"]
|
|
*/
|
|
static int handle_query_view_command(cJSON* command_array, cJSON* response_data) {
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: handle_query_view_command called");
|
|
|
|
// Get view name from command array
|
|
cJSON* view_name_obj = cJSON_GetArrayItem(command_array, 1);
|
|
if (!view_name_obj || !cJSON_IsString(view_name_obj)) {
|
|
app_log(LOG_ERROR, "ADMIN_EVENT: View name missing or not a string");
|
|
cJSON_AddStringToObject(response_data, "status", "error");
|
|
cJSON_AddStringToObject(response_data, "error", "View name required");
|
|
return -1;
|
|
}
|
|
|
|
const char* view_name = cJSON_GetStringValue(view_name_obj);
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: Querying view: %s", view_name);
|
|
|
|
// Validate view name (whitelist approach for security)
|
|
const char* allowed_views[] = {
|
|
"blob_overview",
|
|
"blob_type_distribution",
|
|
"blob_time_stats",
|
|
"top_uploaders",
|
|
NULL
|
|
};
|
|
|
|
int view_allowed = 0;
|
|
for (int i = 0; allowed_views[i] != NULL; i++) {
|
|
if (strcmp(view_name, allowed_views[i]) == 0) {
|
|
view_allowed = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!view_allowed) {
|
|
cJSON_AddStringToObject(response_data, "status", "error");
|
|
cJSON_AddStringToObject(response_data, "error", "Invalid view name");
|
|
app_log(LOG_WARN, "ADMIN_EVENT: Attempted to query invalid view: %s", view_name);
|
|
return -1;
|
|
}
|
|
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: View '%s' is allowed, opening database: %s", view_name, g_db_path);
|
|
|
|
// Open database
|
|
sqlite3* db;
|
|
int rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
app_log(LOG_ERROR, "ADMIN_EVENT: Failed to open database: %s (error: %s)", g_db_path, sqlite3_errmsg(db));
|
|
cJSON_AddStringToObject(response_data, "status", "error");
|
|
cJSON_AddStringToObject(response_data, "error", "Database error");
|
|
return -1;
|
|
}
|
|
|
|
// Build SQL query
|
|
char sql[256];
|
|
snprintf(sql, sizeof(sql), "SELECT * FROM %s", view_name);
|
|
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: Executing SQL: %s", sql);
|
|
|
|
sqlite3_stmt* stmt;
|
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
|
|
app_log(LOG_ERROR, "ADMIN_EVENT: Failed to prepare query: %s (error: %s)", sql, sqlite3_errmsg(db));
|
|
sqlite3_close(db);
|
|
cJSON_AddStringToObject(response_data, "status", "error");
|
|
cJSON_AddStringToObject(response_data, "error", "Failed to prepare query");
|
|
return -1;
|
|
}
|
|
|
|
// Get column count and names
|
|
int col_count = sqlite3_column_count(stmt);
|
|
|
|
// Create results array
|
|
cJSON* results = cJSON_CreateArray();
|
|
|
|
// Fetch all rows
|
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
cJSON* row = cJSON_CreateObject();
|
|
|
|
for (int i = 0; i < col_count; i++) {
|
|
const char* col_name = sqlite3_column_name(stmt, i);
|
|
int col_type = sqlite3_column_type(stmt, i);
|
|
|
|
switch (col_type) {
|
|
case SQLITE_INTEGER:
|
|
cJSON_AddNumberToObject(row, col_name, (double)sqlite3_column_int64(stmt, i));
|
|
break;
|
|
case SQLITE_FLOAT:
|
|
cJSON_AddNumberToObject(row, col_name, sqlite3_column_double(stmt, i));
|
|
break;
|
|
case SQLITE_TEXT:
|
|
cJSON_AddStringToObject(row, col_name, (const char*)sqlite3_column_text(stmt, i));
|
|
break;
|
|
case SQLITE_NULL:
|
|
cJSON_AddNullToObject(row, col_name);
|
|
break;
|
|
default:
|
|
// For BLOB or unknown types, skip
|
|
break;
|
|
}
|
|
}
|
|
|
|
cJSON_AddItemToArray(results, row);
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
sqlite3_close(db);
|
|
|
|
// Build response
|
|
cJSON_AddStringToObject(response_data, "status", "success");
|
|
cJSON_AddStringToObject(response_data, "view_name", view_name);
|
|
cJSON_AddItemToObject(response_data, "data", results);
|
|
|
|
app_log(LOG_DEBUG, "ADMIN_EVENT: Query view '%s' returned %d rows", view_name, cJSON_GetArraySize(results));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Send Kind 23459 admin response event
|
|
*/
|
|
static int send_admin_response_event(const char* admin_pubkey, const char* request_id,
|
|
cJSON* response_data) {
|
|
// Get server keys
|
|
unsigned char server_privkey[32];
|
|
char server_pubkey[65];
|
|
|
|
if (get_server_privkey(server_privkey) != 0 ||
|
|
get_server_pubkey(server_pubkey, sizeof(server_pubkey)) != 0) {
|
|
cJSON_Delete(response_data);
|
|
printf("Status: 500 Internal Server Error\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Failed to get server keys\"}\n");
|
|
return -1;
|
|
}
|
|
|
|
// Convert response data to JSON string
|
|
char* response_json = cJSON_PrintUnformatted(response_data);
|
|
cJSON_Delete(response_data);
|
|
|
|
if (!response_json) {
|
|
printf("Status: 500 Internal Server Error\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Failed to serialize response\"}\n");
|
|
return -1;
|
|
}
|
|
|
|
// Convert admin pubkey to bytes for encryption
|
|
unsigned char admin_pubkey_bytes[32];
|
|
if (nostr_hex_to_bytes(admin_pubkey, admin_pubkey_bytes, 32) != 0) {
|
|
free(response_json);
|
|
printf("Status: 500 Internal Server Error\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Invalid admin pubkey\"}\n");
|
|
return -1;
|
|
}
|
|
|
|
// Encrypt response using NIP-44
|
|
char encrypted_response[131072];
|
|
int encrypt_result = nostr_nip44_encrypt(
|
|
server_privkey,
|
|
admin_pubkey_bytes,
|
|
response_json,
|
|
encrypted_response,
|
|
sizeof(encrypted_response)
|
|
);
|
|
|
|
free(response_json);
|
|
|
|
if (encrypt_result != 0) {
|
|
printf("Status: 500 Internal Server Error\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Failed to encrypt response\"}\n");
|
|
return -1;
|
|
}
|
|
|
|
// Create Kind 23459 response event
|
|
cJSON* response_event = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(response_event, "pubkey", server_pubkey);
|
|
cJSON_AddNumberToObject(response_event, "created_at", (double)time(NULL));
|
|
cJSON_AddNumberToObject(response_event, "kind", 23459);
|
|
cJSON_AddStringToObject(response_event, "content", encrypted_response);
|
|
|
|
// Add tags
|
|
cJSON* tags = cJSON_CreateArray();
|
|
|
|
// p tag for admin
|
|
cJSON* p_tag = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(p_tag, cJSON_CreateString("p"));
|
|
cJSON_AddItemToArray(p_tag, cJSON_CreateString(admin_pubkey));
|
|
cJSON_AddItemToArray(tags, p_tag);
|
|
|
|
// e tag for request correlation
|
|
cJSON* e_tag = cJSON_CreateArray();
|
|
cJSON_AddItemToArray(e_tag, cJSON_CreateString("e"));
|
|
cJSON_AddItemToArray(e_tag, cJSON_CreateString(request_id));
|
|
cJSON_AddItemToArray(tags, e_tag);
|
|
|
|
cJSON_AddItemToObject(response_event, "tags", tags);
|
|
|
|
// Sign the event
|
|
cJSON* signed_event = nostr_create_and_sign_event(
|
|
23459,
|
|
encrypted_response,
|
|
tags,
|
|
server_privkey,
|
|
time(NULL)
|
|
);
|
|
|
|
cJSON_Delete(response_event);
|
|
|
|
if (!signed_event) {
|
|
printf("Status: 500 Internal Server Error\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Failed to sign response event\"}\n");
|
|
return -1;
|
|
}
|
|
|
|
// Return the signed event as HTTP response
|
|
char* event_json = cJSON_PrintUnformatted(signed_event);
|
|
cJSON_Delete(signed_event);
|
|
|
|
if (!event_json) {
|
|
printf("Status: 500 Internal Server Error\r\n");
|
|
printf("Content-Type: application/json\r\n\r\n");
|
|
printf("{\"error\":\"Failed to serialize event\"}\n");
|
|
return -1;
|
|
}
|
|
|
|
printf("Status: 200 OK\r\n");
|
|
printf("Content-Type: application/json\r\n");
|
|
printf("Cache-Control: no-cache\r\n");
|
|
printf("\r\n");
|
|
printf("%s\n", event_json);
|
|
|
|
free(event_json);
|
|
return 0;
|
|
} |