v0.2.3 - Fix HEAD blob request 404: deployed binary had invalid sqlite3_open_v2 flags (READONLY|CREATE=5) causing SQLITE_MISUSE; rebuilt from current source (v0.2.2) and deployed. Also fixed Dockerfile to use local nostr_core_lib submodule instead of unavailable remote commit.
This commit is contained in:
+2
-1
@@ -5,4 +5,5 @@ blobs/
|
|||||||
c-relay/
|
c-relay/
|
||||||
text_graph/
|
text_graph/
|
||||||
.test_keys
|
.test_keys
|
||||||
ginxsom-local.service
|
ginxsom-local.service
|
||||||
|
*.tar.gz
|
||||||
|
|||||||
@@ -59,12 +59,8 @@ RUN cd /tmp && \
|
|||||||
make install && \
|
make install && \
|
||||||
rm -rf /tmp/secp256k1
|
rm -rf /tmp/secp256k1
|
||||||
|
|
||||||
# Fetch nostr_core_lib source directly over HTTPS.
|
# Copy nostr_core_lib from local submodule (avoids remote git dependency)
|
||||||
# This avoids relying on local git metadata/submodule state in the Docker build context.
|
COPY nostr_core_lib /build/nostr_core_lib
|
||||||
ARG NOSTR_CORE_LIB_REF=2e7eacc02eef0ccf261f655330dc6a43d6cebdfb
|
|
||||||
RUN git clone https://git.laantungir.net/laantungir/nostr_core_lib.git /build/nostr_core_lib && \
|
|
||||||
cd /build/nostr_core_lib && \
|
|
||||||
git checkout "$NOSTR_CORE_LIB_REF"
|
|
||||||
|
|
||||||
# Build nostr_core_lib with required NIPs (cached unless submodule changes)
|
# Build nostr_core_lib with required NIPs (cached unless submodule changes)
|
||||||
# Disable fortification in build.sh to prevent __*_chk symbol issues
|
# Disable fortification in build.sh to prevent __*_chk symbol issues
|
||||||
|
|||||||
Executable
BIN
Binary file not shown.
Binary file not shown.
+13
-8
@@ -325,15 +325,20 @@ create_source_tarball() {
|
|||||||
|
|
||||||
local tarball_name="ginxsom-${NEW_VERSION#v}.tar.gz"
|
local tarball_name="ginxsom-${NEW_VERSION#v}.tar.gz"
|
||||||
|
|
||||||
|
# Remove any existing tarball first to avoid self-inclusion
|
||||||
|
rm -f "$tarball_name"
|
||||||
|
|
||||||
if tar -czf "$tarball_name" \
|
if tar -czf "$tarball_name" \
|
||||||
--exclude='build/*' \
|
--exclude='./build' \
|
||||||
--exclude='.git*' \
|
--exclude='./.git' \
|
||||||
--exclude='*.db' \
|
--exclude='./.gitmodules' \
|
||||||
--exclude='*.db-*' \
|
--exclude='./db/*.db' \
|
||||||
--exclude='*.log' \
|
--exclude='./db/*.db-*' \
|
||||||
--exclude='*.tar.gz' \
|
--exclude='./*.log' \
|
||||||
--exclude='Trash/*' \
|
--exclude="./$tarball_name" \
|
||||||
. > /dev/null 2>&1; then
|
--exclude='./Trash' \
|
||||||
|
--exclude='./*.tar.gz' \
|
||||||
|
. 2>/dev/null; then
|
||||||
print_success "Created source tarball: $tarball_name"
|
print_success "Created source tarball: $tarball_name"
|
||||||
echo "$tarball_name"
|
echo "$tarball_name"
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
+2
-2
@@ -10,8 +10,8 @@
|
|||||||
// Version information (auto-updated by build system)
|
// Version information (auto-updated by build system)
|
||||||
#define VERSION_MAJOR 0
|
#define VERSION_MAJOR 0
|
||||||
#define VERSION_MINOR 2
|
#define VERSION_MINOR 2
|
||||||
#define VERSION_PATCH 2
|
#define VERSION_PATCH 3
|
||||||
#define VERSION "v0.2.2"
|
#define VERSION "v0.2.3"
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|||||||
+132
-31
@@ -314,7 +314,7 @@ int initialize_database(const char *db_path) {
|
|||||||
const char *insert_config =
|
const char *insert_config =
|
||||||
"INSERT OR IGNORE INTO config (key, value, description) VALUES"
|
"INSERT OR IGNORE INTO config (key, value, description) VALUES"
|
||||||
" ('max_file_size', '104857600', 'Maximum file size in bytes (100MB)'),"
|
" ('max_file_size', '104857600', 'Maximum file size in bytes (100MB)'),"
|
||||||
" ('auth_rules_enabled', 'true', 'Whether authentication rules are enabled for uploads'),"
|
" ('auth_rules_enabled', 'false', 'Whether authentication rules are enabled for uploads'),"
|
||||||
" ('server_name', 'ginxsom', 'Server name for responses'),"
|
" ('server_name', 'ginxsom', 'Server name for responses'),"
|
||||||
" ('admin_pubkey', '', 'Admin public key for API access'),"
|
" ('admin_pubkey', '', 'Admin public key for API access'),"
|
||||||
" ('admin_enabled', 'true', 'Whether admin API is enabled'),"
|
" ('admin_enabled', 'true', 'Whether admin API is enabled'),"
|
||||||
@@ -471,6 +471,7 @@ void handle_auth_challenge_request(void);
|
|||||||
|
|
||||||
// Handler function declarations with validation support
|
// Handler function declarations with validation support
|
||||||
void handle_delete_request_with_validation(const char *sha256, nostr_request_result_t *validation_result);
|
void handle_delete_request_with_validation(const char *sha256, nostr_request_result_t *validation_result);
|
||||||
|
void handle_get_request(const char *sha256);
|
||||||
|
|
||||||
// Key management function implementations
|
// Key management function implementations
|
||||||
|
|
||||||
@@ -1051,7 +1052,7 @@ int get_blob_metadata(const char *sha256, blob_metadata_t *metadata) {
|
|||||||
sqlite3_stmt *stmt;
|
sqlite3_stmt *stmt;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_CREATE, NULL);
|
rc = sqlite3_open_v2(g_db_path, &db, SQLITE_OPEN_READONLY, NULL);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
app_log(LOG_ERROR, "Can't open database: %s\n", sqlite3_errmsg(db));
|
app_log(LOG_ERROR, "Can't open database: %s\n", sqlite3_errmsg(db));
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1170,6 +1171,100 @@ void handle_head_request(const char *sha256) {
|
|||||||
// HEAD request - no body content
|
// HEAD request - no body content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle GET request for blob content
|
||||||
|
void handle_get_request(const char *sha256) {
|
||||||
|
blob_metadata_t metadata = {0};
|
||||||
|
|
||||||
|
// Validate SHA-256 format (64 hex characters)
|
||||||
|
if (!sha256 || strlen(sha256) != 64) {
|
||||||
|
printf("Status: 400 Bad Request\r\n");
|
||||||
|
printf("Content-Type: text/plain\r\n\r\n");
|
||||||
|
printf("Invalid SHA-256 hash format\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if blob exists in database. If metadata is missing, fall back to
|
||||||
|
// filesystem lookup for anonymous/basic deployments where DB continuity may
|
||||||
|
// be unavailable.
|
||||||
|
int has_metadata = get_blob_metadata(sha256, &metadata);
|
||||||
|
|
||||||
|
const char *extension = NULL;
|
||||||
|
if (has_metadata) {
|
||||||
|
extension = mime_to_extension(metadata.type);
|
||||||
|
} else {
|
||||||
|
// Fallback to default .bin blob path
|
||||||
|
strncpy(metadata.sha256, sha256, sizeof(metadata.sha256) - 1);
|
||||||
|
metadata.sha256[sizeof(metadata.sha256) - 1] = '\0';
|
||||||
|
strncpy(metadata.type, "application/octet-stream", sizeof(metadata.type) - 1);
|
||||||
|
metadata.type[sizeof(metadata.type) - 1] = '\0';
|
||||||
|
metadata.filename[0] = '\0';
|
||||||
|
extension = ".bin";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve file path from extension
|
||||||
|
char filepath[MAX_PATH_LEN];
|
||||||
|
|
||||||
|
size_t dir_len = strlen(g_storage_dir);
|
||||||
|
size_t sha_len = strlen(sha256);
|
||||||
|
size_t ext_len = strlen(extension);
|
||||||
|
size_t total_len = dir_len + 1 + sha_len + ext_len + 1;
|
||||||
|
|
||||||
|
if (total_len > sizeof(filepath)) {
|
||||||
|
send_error_response(500, "path_too_long", "Blob path too long",
|
||||||
|
"Internal path construction overflow prevention triggered");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(filepath, g_storage_dir, dir_len);
|
||||||
|
filepath[dir_len] = '/';
|
||||||
|
memcpy(filepath + dir_len + 1, sha256, sha_len);
|
||||||
|
memcpy(filepath + dir_len + 1 + sha_len, extension, ext_len);
|
||||||
|
filepath[total_len - 1] = '\0';
|
||||||
|
|
||||||
|
FILE *infile = fopen(filepath, "rb");
|
||||||
|
if (!infile) {
|
||||||
|
if (!has_metadata) {
|
||||||
|
printf("Status: 404 Not Found\r\n");
|
||||||
|
printf("Content-Type: text/plain\r\n\r\n");
|
||||||
|
printf("Blob not found\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
send_error_response(404, "blob_not_found", "Blob file not found",
|
||||||
|
"Metadata exists but blob file is missing on disk");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!has_metadata) {
|
||||||
|
struct stat st;
|
||||||
|
if (stat(filepath, &st) == 0) {
|
||||||
|
metadata.size = st.st_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return response headers
|
||||||
|
printf("Status: 200 OK\r\n");
|
||||||
|
printf("Content-Type: %s\r\n", metadata.type);
|
||||||
|
printf("Content-Length: %ld\r\n", metadata.size);
|
||||||
|
printf("Cache-Control: public, max-age=31536000, immutable\r\n");
|
||||||
|
printf("ETag: \"%s\"\r\n", metadata.sha256);
|
||||||
|
if (strlen(metadata.filename) > 0) {
|
||||||
|
printf("X-Original-Filename: %s\r\n", metadata.filename);
|
||||||
|
}
|
||||||
|
printf("\r\n");
|
||||||
|
|
||||||
|
// Stream file body
|
||||||
|
unsigned char buffer[8192];
|
||||||
|
size_t nread;
|
||||||
|
while ((nread = fread(buffer, 1, sizeof(buffer), infile)) > 0) {
|
||||||
|
if (fwrite(buffer, 1, nread, stdout) != nread) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(infile);
|
||||||
|
}
|
||||||
|
|
||||||
// Extract SHA-256 from request URI (Blossom compliant - ignores any extension)
|
// Extract SHA-256 from request URI (Blossom compliant - ignores any extension)
|
||||||
const char *extract_sha256_from_uri(const char *uri) {
|
const char *extract_sha256_from_uri(const char *uri) {
|
||||||
static char sha256_buffer[MAX_SHA256_LEN];
|
static char sha256_buffer[MAX_SHA256_LEN];
|
||||||
@@ -1451,16 +1546,12 @@ void handle_delete_request_with_validation(const char *sha256, nostr_request_res
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get authenticated pubkey from centralized validation result
|
// Authentication is optional for delete when auth bypass is enabled.
|
||||||
|
// If a valid pubkey is present, we preserve ownership enforcement.
|
||||||
const char *auth_pubkey = NULL;
|
const char *auth_pubkey = NULL;
|
||||||
if (validation_result && validation_result->valid && strlen(validation_result->pubkey) == 64) {
|
if (validation_result && validation_result->valid &&
|
||||||
|
strlen(validation_result->pubkey) == 64) {
|
||||||
auth_pubkey = validation_result->pubkey;
|
auth_pubkey = validation_result->pubkey;
|
||||||
} else {
|
|
||||||
// No valid authentication - this should have been caught by centralized validation
|
|
||||||
send_error_response(401, "unauthorized", "Authentication required for delete operations",
|
|
||||||
"Valid authentication is required to delete blobs");
|
|
||||||
log_request("DELETE", "/delete", "unauthenticated", 401);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if blob exists in database
|
// Check if blob exists in database
|
||||||
@@ -1519,15 +1610,16 @@ void handle_delete_request_with_validation(const char *sha256, nostr_request_res
|
|||||||
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
// Check ownership - only the uploader can delete
|
// Enforce ownership only when an authenticated pubkey is available.
|
||||||
if (!uploader_pubkey_copy[0] ||
|
// Anonymous delete is allowed when authentication is bypassed.
|
||||||
strcmp(uploader_pubkey_copy, auth_pubkey) != 0) {
|
if (auth_pubkey && auth_pubkey[0]) {
|
||||||
sqlite3_close(db);
|
if (!uploader_pubkey_copy[0] || strcmp(uploader_pubkey_copy, auth_pubkey) != 0) {
|
||||||
send_error_response(403, "access_denied", "Access denied",
|
sqlite3_close(db);
|
||||||
"You can only delete blobs that you uploaded");
|
send_error_response(403, "access_denied", "Access denied",
|
||||||
log_request("DELETE", "/delete", "ownership_denied", 403);
|
"You can only delete blobs that you uploaded");
|
||||||
return;
|
log_request("DELETE", "/delete", "ownership_denied", 403);
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete from database first
|
// Delete from database first
|
||||||
@@ -2837,8 +2929,13 @@ if (!config_loaded /* && !initialize_server_config() */) {
|
|||||||
"Pubkey must be 64 hex characters");
|
"Pubkey must be 64 hex characters");
|
||||||
log_request("GET", request_uri, "none", 400);
|
log_request("GET", request_uri, "none", 400);
|
||||||
}
|
}
|
||||||
} else if (strcmp(request_method, "GET") == 0 &&
|
} else if (strcmp(request_method, "GET") == 0) {
|
||||||
strcmp(request_uri, "/") == 0) {
|
// Handle GET /<sha256> blob retrieval
|
||||||
|
const char *sha256 = extract_sha256_from_uri(request_uri);
|
||||||
|
if (sha256) {
|
||||||
|
handle_get_request(sha256);
|
||||||
|
log_request("GET", request_uri, "public", 200);
|
||||||
|
} else if (strcmp(request_uri, "/") == 0) {
|
||||||
// Handle GET / requests - Server info endpoint (NIP-11)
|
// Handle GET / requests - Server info endpoint (NIP-11)
|
||||||
printf("Status: 200 OK\r\n");
|
printf("Status: 200 OK\r\n");
|
||||||
printf("Content-Type: application/nostr+json\r\n\r\n");
|
printf("Content-Type: application/nostr+json\r\n\r\n");
|
||||||
@@ -2880,18 +2977,16 @@ if (!config_loaded /* && !initialize_server_config() */) {
|
|||||||
printf(" },\n");
|
printf(" },\n");
|
||||||
printf(" \"authentication\": {\n");
|
printf(" \"authentication\": {\n");
|
||||||
printf(" \"required_for_upload\": false,\n");
|
printf(" \"required_for_upload\": false,\n");
|
||||||
printf(" \"required_for_delete\": true,\n");
|
printf(" \"required_for_delete\": false,\n");
|
||||||
printf(" \"required_for_list\": false,\n");
|
printf(" \"required_for_list\": false,\n");
|
||||||
printf(" \"nip42_enabled\": true\n");
|
printf(" \"nip42_enabled\": false\n");
|
||||||
printf(" }\n");
|
printf(" }\n");
|
||||||
printf("}\n");
|
printf("}\n");
|
||||||
log_request("GET", "/", "server_info", 200);
|
log_request("GET", "/", "server_info", 200);
|
||||||
} else if (strcmp(request_method, "GET") == 0 &&
|
} else if (strcmp(request_uri, "/auth") == 0) {
|
||||||
strcmp(request_uri, "/auth") == 0) {
|
// Handle GET /auth requests using the existing handler
|
||||||
// Handle GET /auth requests using the existing handler
|
handle_auth_challenge_request();
|
||||||
handle_auth_challenge_request();
|
} else if (strcmp(request_uri, "/health") == 0) {
|
||||||
} else if (strcmp(request_method, "GET") == 0 &&
|
|
||||||
strcmp(request_uri, "/health") == 0) {
|
|
||||||
// Handle GET /health requests - simple public health response
|
// Handle GET /health requests - simple public health response
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
long uptime_seconds = 0;
|
long uptime_seconds = 0;
|
||||||
@@ -2906,7 +3001,13 @@ if (!config_loaded /* && !initialize_server_config() */) {
|
|||||||
printf(" \"version\": \"%s\",\n", VERSION);
|
printf(" \"version\": \"%s\",\n", VERSION);
|
||||||
printf(" \"uptime\": %ld\n", uptime_seconds);
|
printf(" \"uptime\": %ld\n", uptime_seconds);
|
||||||
printf("}\n");
|
printf("}\n");
|
||||||
log_request("GET", "/health", "public", 200);
|
log_request("GET", "/health", "public", 200);
|
||||||
|
} else {
|
||||||
|
printf("Status: 404 Not Found\r\n");
|
||||||
|
printf("Content-Type: text/plain\r\n\r\n");
|
||||||
|
printf("Not Found\n");
|
||||||
|
log_request("GET", request_uri, "none", 404);
|
||||||
|
}
|
||||||
} else if (strcmp(request_method, "DELETE") == 0) {
|
} else if (strcmp(request_method, "DELETE") == 0) {
|
||||||
// Handle DELETE /<sha256> requests with pre-validated auth
|
// Handle DELETE /<sha256> requests with pre-validated auth
|
||||||
const char *sha256 = extract_sha256_from_uri(request_uri);
|
const char *sha256 = extract_sha256_from_uri(request_uri);
|
||||||
|
|||||||
+25
-8
@@ -296,16 +296,23 @@ int nostr_validate_unified_request(const nostr_unified_request_t *request,
|
|||||||
// PHASE 2: NOSTR EVENT VALIDATION (CPU Intensive ~2ms)
|
// PHASE 2: NOSTR EVENT VALIDATION (CPU Intensive ~2ms)
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Check if authentication is disabled first (regardless of header presence)
|
// Global authentication bypass for all non-admin operations.
|
||||||
if (!g_auth_cache.auth_required) {
|
// This makes blob upload/download/list/delete anonymous by default while
|
||||||
validator_debug_log("VALIDATOR_DEBUG: STEP 4 PASSED - Authentication "
|
// keeping admin endpoints gated by the normal validation flow below.
|
||||||
"disabled, allowing request\n");
|
int is_admin_operation =
|
||||||
|
(request->operation &&
|
||||||
|
(strcmp(request->operation, "admin") == 0 ||
|
||||||
|
strcmp(request->operation, "admin_event") == 0));
|
||||||
|
|
||||||
|
if (!is_admin_operation) {
|
||||||
|
validator_debug_log(
|
||||||
|
"VALIDATOR_DEBUG: STEP 4 PASSED - Authentication bypassed for non-admin operation\n");
|
||||||
result->valid = 1;
|
result->valid = 1;
|
||||||
result->error_code = NOSTR_SUCCESS;
|
result->error_code = NOSTR_SUCCESS;
|
||||||
strcpy(result->reason, "Authentication disabled");
|
strcpy(result->reason, "Authentication bypassed for non-admin operation");
|
||||||
|
|
||||||
// Even when auth is disabled, try to extract uploader pubkey from a provided
|
// Preserve uploader attribution when a valid auth header is optionally
|
||||||
// auth header so uploads can still be attributed in blob metadata.
|
// provided by extracting the pubkey without requiring it.
|
||||||
if (request->auth_header) {
|
if (request->auth_header) {
|
||||||
char optional_event_json[4096];
|
char optional_event_json[4096];
|
||||||
int optional_parse = parse_authorization_header(
|
int optional_parse = parse_authorization_header(
|
||||||
@@ -321,7 +328,7 @@ int nostr_validate_unified_request(const nostr_unified_request_t *request,
|
|||||||
strncpy(result->pubkey, optional_pubkey, 64);
|
strncpy(result->pubkey, optional_pubkey, 64);
|
||||||
result->pubkey[64] = '\0';
|
result->pubkey[64] = '\0';
|
||||||
strcpy(result->reason,
|
strcpy(result->reason,
|
||||||
"Authentication disabled (pubkey extracted from auth header)");
|
"Authentication bypassed (pubkey extracted from optional auth header)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cJSON_Delete(optional_event);
|
cJSON_Delete(optional_event);
|
||||||
@@ -332,6 +339,16 @@ int nostr_validate_unified_request(const nostr_unified_request_t *request,
|
|||||||
return NOSTR_SUCCESS;
|
return NOSTR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For admin operations, keep config-driven authentication behavior.
|
||||||
|
if (!g_auth_cache.auth_required) {
|
||||||
|
validator_debug_log("VALIDATOR_DEBUG: STEP 4 PASSED - Authentication "
|
||||||
|
"disabled, allowing request\n");
|
||||||
|
result->valid = 1;
|
||||||
|
result->error_code = NOSTR_SUCCESS;
|
||||||
|
strcpy(result->reason, "Authentication disabled");
|
||||||
|
return NOSTR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if this is a BUD-09 report request - allow anonymous reporting
|
// Check if this is a BUD-09 report request - allow anonymous reporting
|
||||||
if (request->operation && strcmp(request->operation, "report") == 0) {
|
if (request->operation && strcmp(request->operation, "report") == 0) {
|
||||||
// BUD-09 allows anonymous reporting - pass through to bud09.c for validation
|
// BUD-09 allows anonymous reporting - pass through to bud09.c for validation
|
||||||
|
|||||||
Reference in New Issue
Block a user