From 30473100b83df3aab05daa73fad1b91cc2cee642 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 10 Sep 2025 07:58:57 -0400 Subject: [PATCH] I think nip42 is FINALLY working. --- 42.md | 109 ++ AGENTS.md | 8 +- build/admin_api.o | Bin 33712 -> 33136 bytes build/bud04.o | Bin 20736 -> 19160 bytes build/bud06.o | Bin 11768 -> 10032 bytes build/ginxsom-fcgi | Bin 228800 -> 228928 bytes build/main.o | Bin 70456 -> 60264 bytes build/request_validator.o | Bin 37576 -> 47304 bytes db/ginxsom.db | Bin 36864 -> 36864 bytes db/schema.sql | 4 +- debug_auth.log | 115 ++ src/admin_api.c | 31 +- src/bud04.c | 54 +- src/bud06.c | 42 +- src/ginxsom.h | 27 +- src/main.c | 2672 +++++++++++++--------------- src/request_validator.c | 1934 +++++++++++++------- tests/auth_test.sh | 16 +- tests/auth_test_tmp/nip42_test.txt | 2 +- tests/mirror_test_bud04.sh | 26 +- 20 files changed, 2846 insertions(+), 2194 deletions(-) create mode 100644 42.md diff --git a/42.md b/42.md new file mode 100644 index 0000000..24f7f5d --- /dev/null +++ b/42.md @@ -0,0 +1,109 @@ +NIP-42 +====== + +Authentication of clients to relays +----------------------------------- + +`draft` `optional` + +This NIP defines a way for clients to authenticate to relays by signing an ephemeral event. + +## Motivation + +A relay may want to require clients to authenticate to access restricted resources. For example, + + - A relay may request payment or other forms of whitelisting to publish events -- this can naïvely be achieved by limiting publication to events signed by the whitelisted key, but with this NIP they may choose to accept any events as long as they are published from an authenticated user; + - A relay may limit access to `kind: 4` DMs to only the parties involved in the chat exchange, and for that it may require authentication before clients can query for that kind. + - A relay may limit subscriptions of any kind to paying users or users whitelisted through any other means, and require authentication. + +## Definitions + +### New client-relay protocol messages + +This NIP defines a new message, `AUTH`, which relays CAN send when they support authentication and clients can send to relays when they want to authenticate. When sent by relays the message has the following form: + +``` +["AUTH", ] +``` + +And, when sent by clients, the following form: + +``` +["AUTH", ] +``` + +Clients MAY provide signed events from multiple pubkeys in a sequence of `AUTH` messages. Relays MUST treat all pubkeys as authenticated accordingly. + +`AUTH` messages sent by clients MUST be answered with an `OK` message, like any `EVENT` message. + +### Canonical authentication event + +The signed event is an ephemeral event not meant to be published or queried, it must be of `kind: 22242` and it should have at least two tags, one for the relay URL and one for the challenge string as received from the relay. Relays MUST exclude `kind: 22242` events from being broadcasted to any client. `created_at` should be the current time. Example: + +```jsonc +{ + "kind": 22242, + "tags": [ + ["relay", "wss://relay.example.com/"], + ["challenge", "challengestringhere"] + ], + // other fields... +} +``` + +### `OK` and `CLOSED` machine-readable prefixes + +This NIP defines two new prefixes that can be used in `OK` (in response to event writes by clients) and `CLOSED` (in response to rejected subscriptions by clients): + +- `"auth-required: "` - for when a client has not performed `AUTH` and the relay requires that to fulfill the query or write the event. +- `"restricted: "` - for when a client has already performed `AUTH` but the key used to perform it is still not allowed by the relay or is exceeding its authorization. + +## Protocol flow + +At any moment the relay may send an `AUTH` message to the client containing a challenge. The challenge is valid for the duration of the connection or until another challenge is sent by the relay. The client MAY decide to send its `AUTH` event at any point and the authenticated session is valid afterwards for the duration of the connection. + +### `auth-required` in response to a `REQ` message + +Given that a relay is likely to require clients to perform authentication only for certain jobs, like answering a `REQ` or accepting an `EVENT` write, these are some expected common flows: + +``` +relay: ["AUTH", ""] +client: ["REQ", "sub_1", {"kinds": [4]}] +relay: ["CLOSED", "sub_1", "auth-required: we can't serve DMs to unauthenticated users"] +client: ["AUTH", {"id": "abcdef...", ...}] +client: ["AUTH", {"id": "abcde2...", ...}] +relay: ["OK", "abcdef...", true, ""] +relay: ["OK", "abcde2...", true, ""] +client: ["REQ", "sub_1", {"kinds": [4]}] +relay: ["EVENT", "sub_1", {...}] +relay: ["EVENT", "sub_1", {...}] +relay: ["EVENT", "sub_1", {...}] +relay: ["EVENT", "sub_1", {...}] +... +``` + +In this case, the `AUTH` message from the relay could be sent right as the client connects or it can be sent immediately before the `CLOSED` is sent. The only requirement is that _the client must have a stored challenge associated with that relay_ so it can act upon that in response to the `auth-required` `CLOSED` message. + +### `auth-required` in response to an `EVENT` message + +The same flow is valid for when a client wants to write an `EVENT` to the relay, except now the relay sends back an `OK` message instead of a `CLOSED` message: + +``` +relay: ["AUTH", ""] +client: ["EVENT", {"id": "012345...", ...}] +relay: ["OK", "012345...", false, "auth-required: we only accept events from registered users"] +client: ["AUTH", {"id": "abcdef...", ...}] +relay: ["OK", "abcdef...", true, ""] +client: ["EVENT", {"id": "012345...", ...}] +relay: ["OK", "012345...", true, ""] +``` + +## Signed Event Verification + +To verify `AUTH` messages, relays must ensure: + + - that the `kind` is `22242`; + - that the event `created_at` is close (e.g. within ~10 minutes) of the current time; + - that the `"challenge"` tag matches the challenge sent before; + - that the `"relay"` tag matches the relay URL: + - URL normalization techniques can be applied. For most cases just checking if the domain name is correct should be enough. diff --git a/AGENTS.md b/AGENTS.md index 71cda88..5fc74fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,13 +12,13 @@ This file provides guidance to agents when working with code in this repository. - **Admin Auth**: Uses Nostr events for admin API authentication (kind 24242 with "admin" tag) - **Blob Storage**: Files stored as `blobs/.` where extension comes from MIME type - **Build Directory**: Must create `build/` directory before compilation -- **Test Files**: Pre-existing test files in `blobs/` with specific SHA-256 names -- **Server Private Key**: Stored in memory only, never in database (security requirement) +- **Test Files**: Tests are in the tests/ directory, and they should be run from the root, i.e. 'tests/mirror_test_bud04.sh' + ## Non-Standard Commands ```bash -# Restart nginx (local only) +# make clean && make && Restart nginx ./restart-all.sh # Start FastCGI daemon @@ -30,8 +30,6 @@ source .admin_keys && ./scripts/test_admin.sh # Setup wizard (creates signed config events) ./scripts/setup.sh -# Local SQLite (not system) -./sqlite3-build/sqlite3 db/ginxsom.db ``` ## Critical Architecture Notes diff --git a/build/admin_api.o b/build/admin_api.o index eb732f569e24d72fdbe4a87064cff0e1dd4239f7..fec0ba34be516f087bf6c40463b6e89cf37eba45 100644 GIT binary patch literal 33136 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 YET{j5lB}=vJ9)CB?IyL${&lARKl$p}kpKVy literal 33712 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 diff --git a/build/bud04.o b/build/bud04.o index b7c22b9d26fc2ad1230a478faa34b4dce4043138..57da53461ca8175479b98e8965a08bb8b1a4b9ee 100644 GIT binary patch literal 19160 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 F_dgp3e1-r3 delta 3182 zcmZ`*eN0nV6u&JNC8i62dTJ;NDH_rW@ctnvN&~WGP4hN?s>Oxt=S9w?!CYB zJHK=8?d`|$_CL2w+4NtmI8l+CTD&ub5T7!+sLVzPQDWor01#EP?)YbN58a%4B+Ax4 zWu+!CO|u%iI*xmTbX_Ivfa*2D=DW|th%&HYtvr2qMX5`6r0QZzn6tH7! zckJ9v7|6t|X`)L9PVx0@iJSuDcqJoaPT8g~w|>*@D0pQtjcJ8bUd{DPQO6WH$~mVz z$ij?jVAK*N=9VS4R9H#}gC4B_Z$gRh)&v8*mxya}Tpj}xcf1S`x>Ku_$u6*(RHIO1 za<`n0(v#GyxI-I0!nK;|j%%~F*sltY#cp6a zZu%`yW2lg&%`5JnI-nzV?Ql-Q6e_c_N*rZc^=_!%KgIi=Yk9JJ8nRiGRD`uxMglFuIs_&FAzz{V5c$4&Qh9yhej1&xP52(Y_n=>Ei^j#^mJ<5;QYk+@SK`^9a>XtBb)MO<2-qCDH#5H^6K2ez0X~-5vqu|9X+q&( z2f`GyT#?Te>o|Xn^K-m*S0dA}B{3`MA z=v;J9Q6=Cp#H)x|X2z^X+>Uq%@mn*~yOywM1o30U@0Xkc<{b;;&mulREL+N$O^6eS zpCkT)Sh%dxqChu)!{;q~!u-`>9k$Z$Wvs&pXxaqL(<}%N8#d6dmR0K7=q*sTQbRGD zJBl~x+UXvcx6@;YR}r&H7_%O6dkJ%%DDhZ2z$H=r#Z>02IlJjUB_lc~jV!O+Bx-Wf z7Bo#TP2^fR4_5}yIl;?l>3p_0(JR>B0DI|zT zcR|oUE9fr?Jp28bIMM3@&wh3$_}_qs{cCquEbxbrP%k7r6!_-^KE=pfwAa1?#zcW^ zfo~LeiSuwL^rV?ywVEw1A$*?QA$Zu8aZP7Q%_(}>NIOewG7c`q08tjxw@YjEJ|lby z6wzy?w!~DDZAKq=zfU*HwZ@khYboE*19Gii&ZZaSdi}L>no(A-_hr*o7^bq(=5U#f zO)mg*Jey{e*Xw(1w6(mx(3YcFoaa~H1j{CRsC=dIj#-Nwp_j^QjnWEj^)9_sYW)wW Cv^&!P diff --git a/build/ginxsom-fcgi b/build/ginxsom-fcgi index 41721251c37185b963f22a0ebd455ef3c4430123..1df162702545a1076973eec9cc4cb3557e743030 100755 GIT binary patch literal 228928 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 literal 228800 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 Zq$|ib5b1TnKGWY)@6sQck{~7M{U0gB_09kQ diff --git a/build/main.o b/build/main.o index 11efc063e7037148c62016e4ab8d97706f5dfc1e..c00abd79e20cf9d134e63f8ff0b83fe750eeb614 100644 GIT binary patch literal 60264 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

JhJw-_2GtJDQlkY-O}AQBDtk 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 Q`rF?y6x;vV1aA8O1y>sKy#N3J literal 70456 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 aS6yw*dUcn66+dL5W0U-!`A>`E1OErJdW=s1 diff --git a/build/request_validator.o b/build/request_validator.o index 191373d2b7c0abc6f8cf8f89c7de674878a55239..eadcf7401d1b0b03c008e81325754d3b244c533a 100644 GIT binary patch literal 47304 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 literal 37576 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 Q^z_EiW3hO$&@tZn7kr7NeE z=Uc_+#y@GZqJt4%av~d>p=>=1OHpcJX=YJsd|qaOiBWuFX-P(YK}lwQUSf`)XMnDW zkpf64HLoNyIT0kRkei>9s-TgQS)7=ZlbWKV08_1_05vH^Gvk0+EeKCuY4=K%kIhh& zfrSNRN_(j1pdiY^<6-Rod#I|GyXl$=o$PIC&+VhFtad%JjyotO1~nvr7#~mGYcn3003 AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68' AUTH: operation: 'upload' AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 1, reason: Blossom authentication passed +AUTH: pubkey extracted: '87d3561f19b74adbe8bf840682992466068830a9d8c36b4a0c99d36f826cb6cb' +AUTH: resource_hash: '79d91386d021284f9e390da6b0797c0f505ed6e5f05a28780c1d05fb2d17bebc' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 1, reason: Blossom authentication passed +AUTH: pubkey extracted: '0396b426090284a28294078dce53fe73791ab623c3fc46ab4409fea05109a6db' +AUTH: resource_hash: 'edba918a6b09d72a3084955bba7ea82057360e2b5378d710a09335e604420049' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 1, reason: Blossom authentication passed +AUTH: pubkey extracted: '87d3561f19b74adbe8bf840682992466068830a9d8c36b4a0c99d36f826cb6cb' +AUTH: resource_hash: '5a5628938aa5fc67b79f5c843c813bf7823f4307935b6eb372f1250c1ccd447d' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 1, reason: Blossom authentication passed +AUTH: pubkey extracted: '769a740386211c76f81bb235de50a5e6fa463cb4fae25e62625607fc2cfc0f28' +AUTH: resource_hash: '92e62f9708cef7d7f4675250267a35182300df6e1c5b6cf0bd207912d94c9016' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: Failed to parse authorization header +AUTH: pubkey extracted: +AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: Failed to parse authorization header +AUTH: pubkey extracted: +AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: Invalid JSON in authorization +AUTH: pubkey extracted: +AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed +AUTH: pubkey extracted: +AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed +AUTH: pubkey extracted: +AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: NOSTR event validation failed +AUTH: pubkey extracted: +AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: Unsupported event kind for authentication +AUTH: pubkey extracted: +AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation +AUTH: pubkey extracted: +AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation +AUTH: pubkey extracted: +AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation +AUTH: pubkey extracted: +AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: Blossom event does not authorize this operation +AUTH: pubkey extracted: +AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: Invalid JSON in authorization +AUTH: pubkey extracted: +AUTH: resource_hash: 'a6c3dfd8af9c4b831fdb05a523a3ea398ba48b5d7213b0adb264aef88fd6bc68' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: NIP-42 authentication is disabled +AUTH: pubkey extracted: +AUTH: resource_hash: 'faa99300df27a428d616596942b728a2ba8d43721701da59289a5cb41b2de006' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: NIP-42 authentication is disabled +AUTH: pubkey extracted: +AUTH: resource_hash: 'faa99300df27a428d616596942b728a2ba8d43721701da59289a5cb41b2de006' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: NIP-42 authentication is disabled +AUTH: pubkey extracted: +AUTH: resource_hash: 'faa99300df27a428d616596942b728a2ba8d43721701da59289a5cb41b2de006' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: nostr_validate_request returned: 0, valid: 0, reason: NIP-42 authentication requires request_url and challenge_id +AUTH: pubkey extracted: +AUTH: resource_hash: 'faa99300df27a428d616596942b728a2ba8d43721701da59289a5cb41b2de006' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: returned: 0, valid: 1, reason: Blossom authentication passed +AUTH: pubkey extracted: '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' +AUTH: resource_hash: '5fd15187a73dadc96dd154c9bc4a60ba93a6cc42a11a855492d4239e697264cd' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: returned: 0, valid: 1, reason: Blossom authentication passed +AUTH: pubkey extracted: '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' +AUTH: resource_hash: '7e7ed5792eb1e148dac5cb28341019e8a7a1b1d62f804154ceb8d524e2d3ee86' +AUTH: operation: 'upload' +AUTH: auth_header present: YES +AUTH: returned: 0, valid: 1, reason: Blossom authentication passed +AUTH: pubkey extracted: '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' +AUTH: resource_hash: '419d982df06e8f629cf163331d98d4c3d18e9341e0c03dbe14f21392f5ffd028' +AUTH: operation: 'upload' +AUTH: auth_header present: YES diff --git a/src/admin_api.c b/src/admin_api.c index 07d2454..41b304a 100644 --- a/src/admin_api.c +++ b/src/admin_api.c @@ -176,32 +176,15 @@ int authenticate_admin_request(const char* auth_header) { return 0; // No auth header } - // Use unified request validation system for admin operations - nostr_request_t request = { - .operation = "admin", - .auth_header = auth_header, - .event = NULL, - .resource_hash = NULL, - .mime_type = NULL, - .file_size = 0, - .client_ip = getenv("REMOTE_ADDR"), - .app_context = NULL - }; + // NOTE: Authentication now handled by centralized validation system in main.c + // This function is kept for compatibility but should receive validation results + // from the centralized system rather than performing duplicate validation - nostr_request_result_t result; - int auth_result = nostr_validate_request(&request, &result); + // TODO: Modify to accept validation results from centralized system + // For now, assume validation was successful if we reach here + // and extract pubkey from global context or parameters - if (auth_result != NOSTR_SUCCESS || !result.valid) { - return 0; // Authentication failed - } - - // Extract pubkey from validation result and verify admin status - const char* event_pubkey = result.pubkey[0] ? result.pubkey : NULL; - if (!event_pubkey) { - return 0; // No pubkey available - } - - return verify_admin_pubkey(event_pubkey); + return 0; // Temporarily disabled - needs integration with centralized system } int verify_admin_pubkey(const char* event_pubkey) { diff --git a/src/bud04.c b/src/bud04.c index 895641b..da24c18 100644 --- a/src/bud04.c +++ b/src/bud04.c @@ -373,53 +373,15 @@ void handle_mirror_request(void) { const char* auth_header = getenv("HTTP_AUTHORIZATION"); const char* expected_hash = NULL; const char* uploader_pubkey = NULL; - static char pubkey_buffer[256]; - + if (auth_header) { - // Use unified request validation system - nostr_request_t request = { - .operation = "upload", - .auth_header = auth_header, - .event = NULL, - .resource_hash = NULL, - .mime_type = NULL, - .file_size = 0, - .client_ip = getenv("REMOTE_ADDR"), - .app_context = NULL - }; - - nostr_request_result_t result; - int auth_result = nostr_validate_request(&request, &result); - - if (auth_result != NOSTR_SUCCESS || !result.valid) { - const char* error_type = "authentication_failed"; - const char* message = "Invalid authentication"; - const char* details = result.reason[0] ? result.reason : "The provided authorization is invalid"; - - // Provide more specific error messages based on the reason - if (strstr(result.reason, "whitelist")) { - error_type = "pubkey_not_whitelisted"; - message = "Public key not authorized"; - } else if (strstr(result.reason, "blacklist")) { - error_type = "access_denied"; - message = "Access denied by policy"; - } - - send_error_response(401, error_type, message, details); - log_request("PUT", "/mirror", "auth_failed", 401); - return; - } - - // Extract uploader pubkey from validation result - if (result.pubkey[0]) { - strncpy(pubkey_buffer, result.pubkey, sizeof(pubkey_buffer)-1); - pubkey_buffer[sizeof(pubkey_buffer)-1] = '\0'; - uploader_pubkey = pubkey_buffer; - } - - // For mirror operations, we don't need to extract the expected hash here - // The unified validation system handles hash validation internally - // We just need the pubkey for metadata storage + // NOTE: Authorization validation now handled by centralized validation system in main.c + // This handler receives pre-validated requests, so if we reach here with auth_header, + // the authentication was already successful + + // TODO: Extract uploader pubkey from centralized validation results + // For now, set a placeholder until integration is complete + uploader_pubkey = "authenticated_user"; } // Download the blob diff --git a/src/bud06.c b/src/bud06.c index 4307918..0b87d89 100644 --- a/src/bud06.c +++ b/src/bud06.c @@ -216,45 +216,9 @@ void handle_head_upload_request(void) { const char* auth_status = "none"; if (auth_header) { - // Validate authorization if provided - nostr_request_t request = { - .operation = "upload", - .auth_header = auth_header, - .event = NULL, - .resource_hash = sha256, - .mime_type = content_type, - .file_size = content_length, - .client_ip = getenv("REMOTE_ADDR"), - .app_context = NULL - }; - - nostr_request_result_t result; - int auth_result = nostr_validate_request(&request, &result); - - if (auth_result != NOSTR_SUCCESS || !result.valid) { - const char* error_type = "authentication_failed"; - const char* message = "Invalid or expired authentication"; - const char* details = result.reason[0] ? result.reason : "Authentication validation failed"; - - // Provide more specific error messages based on the reason - if (strstr(result.reason, "whitelist")) { - error_type = "pubkey_not_whitelisted"; - message = "Public key not authorized"; - details = result.reason; - } else if (strstr(result.reason, "blacklist")) { - error_type = "access_denied"; - message = "Access denied by policy"; - details = result.reason; - } else if (strstr(result.reason, "size")) { - error_type = "file_too_large"; - message = "File size exceeds policy limits"; - details = result.reason; - } - - send_upload_error_response(401, error_type, message, details); - log_request("HEAD", "/upload", "auth_failed", 401); - return; - } + // NOTE: Authorization validation now handled by centralized validation system in main.c + // This handler receives pre-validated requests, so if we reach here with auth_header, + // the authentication was already successful auth_status = "authenticated"; } diff --git a/src/ginxsom.h b/src/ginxsom.h index a77ee0a..602408a 100644 --- a/src/ginxsom.h +++ b/src/ginxsom.h @@ -77,6 +77,21 @@ typedef struct { void* app_context; // Application context (unused, for compatibility) } nostr_request_t; +// Extended request structure for unified API +typedef struct { + const char* operation; // Operation type ("upload", "delete", "list", "publish", "admin") + const char* auth_header; // Raw authorization header (optional) + cJSON* event; // Parsed NOSTR event for validation (optional) + const char* resource_hash; // Resource hash (SHA-256, optional) + const char* mime_type; // MIME type (optional) + long file_size; // File size (optional) + const char* request_url; // Request URL for NIP-42 relay URL validation (optional) + const char* challenge_id; // Challenge ID for NIP-42 verification (optional) + int nip42_enabled; // Whether NIP-42 authentication is enabled + const char* client_ip; // Client IP address (optional) + void* app_context; // Application context (unused, for compatibility) +} nostr_unified_request_t; + typedef struct { int valid; // 0 = invalid/denied, 1 = valid/allowed int error_code; // NOSTR_SUCCESS or specific error code @@ -98,13 +113,19 @@ int nostr_sha256(const unsigned char* data, size_t len, unsigned char* hash); void nostr_bytes_to_hex(const unsigned char* bytes, size_t len, char* hex_out); int nostr_crypto_init(void); -int nostr_validate_request(const nostr_request_t* request, nostr_request_result_t* result); -int nostr_request_validator_init(const char* db_path, const char* app_name); +// Unified API function (main entry point) +int nostr_validate_unified_request(const nostr_unified_request_t* request, nostr_request_result_t* result); +int ginxsom_request_validator_init(const char* db_path, const char* app_name); int nostr_auth_rules_enabled(void); -void nostr_request_validator_cleanup(void); +void ginxsom_request_validator_cleanup(void); void nostr_request_validator_force_cache_refresh(void); int nostr_request_validator_generate_nip42_challenge(void* challenge_struct, const char* client_ip); +// New NIP-42 challenge management functions +int nostr_generate_nip42_challenge(char* challenge_out, size_t challenge_size, const char* client_ip); +const char* nostr_request_validator_get_last_violation_type(void); +void nostr_request_validator_clear_violation(void); + // Upload handling void handle_upload_request(void); diff --git a/src/main.c b/src/main.c index 1beaf48..8c99e64 100644 --- a/src/main.c +++ b/src/main.c @@ -4,18 +4,20 @@ */ #define _GNU_SOURCE +#include "ginxsom.h" +#include "../nostr_core_lib/nostr_core/nostr_common.h" +#include "../nostr_core_lib/nostr_core/utils.h" +#include +#include +#include +#include #include #include #include #include -#include -#include -#include #include #include -#include -#include -#include "ginxsom.h" +#include // Debug macros removed @@ -26,13 +28,12 @@ // Database path #define DB_PATH "db/ginxsom.db" - // Forward declarations for config system int initialize_server_config(void); -int apply_config_from_event(cJSON* event); -int get_config_file_path(char* path, size_t path_size); -int load_server_config(const char* config_path); -int run_interactive_setup(const char* config_path); +int apply_config_from_event(cJSON *event); +int get_config_file_path(char *path, size_t path_size); +int load_server_config(const char *config_path); +int run_interactive_setup(const char *config_path); // Configuration system implementation #include @@ -42,10 +43,9 @@ int run_interactive_setup(const char* config_path); // Server configuration structure typedef struct { - char admin_pubkey[256]; - char admin_enabled[8]; - char require_nip42_auth[16]; // "disabled", "optional", "required" - int config_loaded; + char admin_pubkey[256]; + char admin_enabled[8]; + int config_loaded; } server_config_t; // Global configuration instance @@ -55,469 +55,456 @@ static server_config_t g_server_config = {0}; static char server_private_key[128] = {0}; // Function to get XDG config directory -const char* get_config_dir(char* buffer, size_t buffer_size) { - const char* xdg_config = getenv("XDG_CONFIG_HOME"); - if (xdg_config) { - snprintf(buffer, buffer_size, "%s/ginxsom", xdg_config); - return buffer; - } +const char *get_config_dir(char *buffer, size_t buffer_size) { + const char *xdg_config = getenv("XDG_CONFIG_HOME"); + if (xdg_config) { + snprintf(buffer, buffer_size, "%s/ginxsom", xdg_config); + return buffer; + } - const char* home = getenv("HOME"); - if (home) { - snprintf(buffer, buffer_size, "%s/.config/ginxsom", home); - return buffer; - } + const char *home = getenv("HOME"); + if (home) { + snprintf(buffer, buffer_size, "%s/.config/ginxsom", home); + return buffer; + } - // Fallback - return ".config/ginxsom"; + // Fallback + return ".config/ginxsom"; } // Load server configuration from database or create defaults int initialize_server_config(void) { - sqlite3* db = NULL; - sqlite3_stmt* stmt = NULL; - int rc; + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + int rc; - memset(&g_server_config, 0, sizeof(g_server_config)); + memset(&g_server_config, 0, sizeof(g_server_config)); - // Open database - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc != SQLITE_OK) { - fprintf(stderr, "CONFIG: Could not open database for config: %s\n", sqlite3_errmsg(db)); - // Config database doesn't exist - leave config uninitialized - g_server_config.config_loaded = 0; - return 0; + // Open database + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc != SQLITE_OK) { + fprintf(stderr, "CONFIG: Could not open database for config: %s\n", + sqlite3_errmsg(db)); + // Config database doesn't exist - leave config uninitialized + g_server_config.config_loaded = 0; + return 0; + } + + // Load admin_pubkey + const char *sql = "SELECT value FROM config WHERE key = ?"; + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC); + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + const char *value = (const char *)sqlite3_column_text(stmt, 0); + if (value) { + strncpy(g_server_config.admin_pubkey, value, + sizeof(g_server_config.admin_pubkey) - 1); + } } + sqlite3_finalize(stmt); + } - // Load admin_pubkey - const char* sql = "SELECT value FROM config WHERE key = ?"; - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC); - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - const char* value = (const char*)sqlite3_column_text(stmt, 0); - if (value) { - strncpy(g_server_config.admin_pubkey, value, sizeof(g_server_config.admin_pubkey) - 1); - } - } - sqlite3_finalize(stmt); + // Load admin_enabled + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, "admin_enabled", -1, SQLITE_STATIC); + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + const char *value = (const char *)sqlite3_column_text(stmt, 0); + if (value && strcmp(value, "true") == 0) { + strcpy(g_server_config.admin_enabled, "true"); + } else { + strcpy(g_server_config.admin_enabled, "false"); + } } + sqlite3_finalize(stmt); + } - // Load admin_enabled - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, "admin_enabled", -1, SQLITE_STATIC); - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - const char* value = (const char*)sqlite3_column_text(stmt, 0); - if (value && strcmp(value, "true") == 0) { - strcpy(g_server_config.admin_enabled, "true"); - } else { - strcpy(g_server_config.admin_enabled, "false"); - } - } - sqlite3_finalize(stmt); - } + sqlite3_close(db); - // Load require_nip42_auth (default: optional) - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, "require_nip42_auth", -1, SQLITE_STATIC); - rc = sqlite3_step(stmt); - if (rc == SQLITE_ROW) { - const char* value = (const char*)sqlite3_column_text(stmt, 0); - if (value) { - strncpy(g_server_config.require_nip42_auth, value, sizeof(g_server_config.require_nip42_auth) - 1); - } else { - strcpy(g_server_config.require_nip42_auth, "optional"); - } - } else { - strcpy(g_server_config.require_nip42_auth, "optional"); - } - sqlite3_finalize(stmt); - } else { - strcpy(g_server_config.require_nip42_auth, "optional"); - } - - sqlite3_close(db); - - g_server_config.config_loaded = 1; - fprintf(stderr, "CONFIG: Server configuration loaded\n"); - return 1; + g_server_config.config_loaded = 1; + fprintf(stderr, "CONFIG: Server configuration loaded\n"); + return 1; } // File-based configuration system // Config file path resolution -int get_config_file_path(char* path, size_t path_size) { - const char* home = getenv("HOME"); - const char* xdg_config = getenv("XDG_CONFIG_HOME"); - - if (xdg_config) { - snprintf(path, path_size, "%s/ginxsom/ginxsom_config_event.json", xdg_config); - } else if (home) { - snprintf(path, path_size, "%s/.config/ginxsom/ginxsom_config_event.json", home); - } else { - return 0; - } - return 1; +int get_config_file_path(char *path, size_t path_size) { + const char *home = getenv("HOME"); + const char *xdg_config = getenv("XDG_CONFIG_HOME"); + + if (xdg_config) { + snprintf(path, path_size, "%s/ginxsom/ginxsom_config_event.json", + xdg_config); + } else if (home) { + snprintf(path, path_size, "%s/.config/ginxsom/ginxsom_config_event.json", + home); + } else { + return 0; + } + return 1; } // Load and validate config event -int load_server_config(const char* config_path) { - FILE* file = fopen(config_path, "r"); - if (!file) { - return 0; // Config file doesn't exist - } - - // Read entire file - fseek(file, 0, SEEK_END); - long file_size = ftell(file); - fseek(file, 0, SEEK_SET); - - char* json_data = malloc(file_size + 1); - if (!json_data) { - fclose(file); - return 0; - } - - fread(json_data, 1, file_size, file); - json_data[file_size] = '\0'; +int load_server_config(const char *config_path) { + FILE *file = fopen(config_path, "r"); + if (!file) { + return 0; // Config file doesn't exist + } + + // Read entire file + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + char *json_data = malloc(file_size + 1); + if (!json_data) { fclose(file); - - // Parse and validate JSON event - cJSON* event = cJSON_Parse(json_data); - free(json_data); - - if (!event) { - fprintf(stderr, "Invalid JSON in config file\n"); - return 0; - } - - // Validate event structure and signature - if (nostr_validate_event(event) != NOSTR_SUCCESS) { - fprintf(stderr, "Invalid or corrupted config event\n"); - cJSON_Delete(event); - return 0; - } - - // Extract configuration and apply to server - int result = apply_config_from_event(event); + return 0; + } + + fread(json_data, 1, file_size, file); + json_data[file_size] = '\0'; + fclose(file); + + // Parse and validate JSON event + cJSON *event = cJSON_Parse(json_data); + free(json_data); + + if (!event) { + fprintf(stderr, "Invalid JSON in config file\n"); + return 0; + } + + // Validate event structure and signature + if (nostr_validate_event(event) != NOSTR_SUCCESS) { + fprintf(stderr, "Invalid or corrupted config event\n"); cJSON_Delete(event); - - return result; + return 0; + } + + // Extract configuration and apply to server + int result = apply_config_from_event(event); + cJSON_Delete(event); + + return result; } // Extract config from validated event and apply to server -int apply_config_from_event(cJSON* event) { - sqlite3* db; - sqlite3_stmt* stmt; - int rc; - - // Open database for config storage - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL); - if (rc) { - fprintf(stderr, "Failed to open database for config\n"); - return 0; - } - - // Extract admin pubkey from event - cJSON* pubkey_json = cJSON_GetObjectItem(event, "pubkey"); - if (!pubkey_json || !cJSON_IsString(pubkey_json)) { - sqlite3_close(db); - return 0; - } - const char* admin_pubkey = cJSON_GetStringValue(pubkey_json); - - // Store admin pubkey in database - const char* insert_sql = "INSERT OR REPLACE INTO config (key, value, description) VALUES (?, ?, ?)"; - rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, admin_pubkey, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 3, "Admin public key from config event", -1, SQLITE_STATIC); - sqlite3_step(stmt); - sqlite3_finalize(stmt); - } - - // Extract server private key and store securely (in memory only) - cJSON* tags = cJSON_GetObjectItem(event, "tags"); - if (tags && cJSON_IsArray(tags)) { - cJSON* tag = NULL; - cJSON_ArrayForEach(tag, tags) { - if (!cJSON_IsArray(tag)) continue; - - cJSON* tag_name = cJSON_GetArrayItem(tag, 0); - cJSON* tag_value = cJSON_GetArrayItem(tag, 1); - - if (!tag_name || !cJSON_IsString(tag_name) || - !tag_value || !cJSON_IsString(tag_value)) continue; - - const char* key = cJSON_GetStringValue(tag_name); - const char* value = cJSON_GetStringValue(tag_value); - - if (strcmp(key, "server_privkey") == 0) { - // Store server private key in global variable (memory only) - strncpy(server_private_key, value, sizeof(server_private_key) - 1); - server_private_key[sizeof(server_private_key) - 1] = '\0'; - } else { - // Store other config values in database - rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, value, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 3, "From config event", -1, SQLITE_STATIC); - sqlite3_step(stmt); - sqlite3_finalize(stmt); - } - } - } - } - +int apply_config_from_event(cJSON *event) { + sqlite3 *db; + sqlite3_stmt *stmt; + int rc; + + // Open database for config storage + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL); + if (rc) { + fprintf(stderr, "Failed to open database for config\n"); + return 0; + } + + // Extract admin pubkey from event + cJSON *pubkey_json = cJSON_GetObjectItem(event, "pubkey"); + if (!pubkey_json || !cJSON_IsString(pubkey_json)) { sqlite3_close(db); - return 1; + return 0; + } + const char *admin_pubkey = cJSON_GetStringValue(pubkey_json); + + // Store admin pubkey in database + const char *insert_sql = "INSERT OR REPLACE INTO config (key, value, " + "description) VALUES (?, ?, ?)"; + rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, "admin_pubkey", -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, admin_pubkey, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, "Admin public key from config event", -1, + SQLITE_STATIC); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + + // Extract server private key and store securely (in memory only) + cJSON *tags = cJSON_GetObjectItem(event, "tags"); + if (tags && cJSON_IsArray(tags)) { + cJSON *tag = NULL; + cJSON_ArrayForEach(tag, tags) { + if (!cJSON_IsArray(tag)) + continue; + + cJSON *tag_name = cJSON_GetArrayItem(tag, 0); + cJSON *tag_value = cJSON_GetArrayItem(tag, 1); + + if (!tag_name || !cJSON_IsString(tag_name) || !tag_value || + !cJSON_IsString(tag_value)) + continue; + + const char *key = cJSON_GetStringValue(tag_name); + const char *value = cJSON_GetStringValue(tag_value); + + if (strcmp(key, "server_privkey") == 0) { + // Store server private key in global variable (memory only) + strncpy(server_private_key, value, sizeof(server_private_key) - 1); + server_private_key[sizeof(server_private_key) - 1] = '\0'; + } else { + // Store other config values in database + rc = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, value, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, "From config event", -1, SQLITE_STATIC); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } + } + + sqlite3_close(db); + return 1; } // Interactive setup runner -int run_interactive_setup(const char* config_path) { - printf("\n=== Ginxsom First-Time Setup Required ===\n"); - printf("No configuration found at: %s\n\n", config_path); - printf("Options:\n"); - printf("1. Run interactive setup wizard\n"); - printf("2. Exit and create config manually\n"); - printf("Choice (1/2): "); - - char choice[10]; - if (!fgets(choice, sizeof(choice), stdin)) { - return 1; - } - - if (choice[0] == '1') { - // Run setup script - char script_path[512]; - snprintf(script_path, sizeof(script_path), "./scripts/setup.sh \"%s\"", config_path); - return system(script_path); - } else { - printf("\nManual setup instructions:\n"); - printf("1. Run: ./scripts/generate_config.sh\n"); - printf("2. Place signed config at: %s\n", config_path); - printf("3. Restart ginxsom\n"); - return 1; - } +int run_interactive_setup(const char *config_path) { + printf("\n=== Ginxsom First-Time Setup Required ===\n"); + printf("No configuration found at: %s\n\n", config_path); + printf("Options:\n"); + printf("1. Run interactive setup wizard\n"); + printf("2. Exit and create config manually\n"); + printf("Choice (1/2): "); + + char choice[10]; + if (!fgets(choice, sizeof(choice), stdin)) { + return 1; + } + + if (choice[0] == '1') { + // Run setup script + char script_path[512]; + snprintf(script_path, sizeof(script_path), "./scripts/setup.sh \"%s\"", + config_path); + return system(script_path); + } else { + printf("\nManual setup instructions:\n"); + printf("1. Run: ./scripts/generate_config.sh\n"); + printf("2. Place signed config at: %s\n", config_path); + printf("3. Restart ginxsom\n"); + return 1; + } } // Function declarations -void send_error_response(int status_code, const char* error_type, const char* message, const char* details); -void log_request(const char* method, const char* uri, const char* auth_status, int status_code); +void send_error_response(int status_code, const char *error_type, + const char *message, const char *details); +void log_request(const char *method, const char *uri, const char *auth_status, + int status_code); // External validator function declarations -const char* nostr_request_validator_get_last_violation_type(void); +const char *nostr_request_validator_get_last_violation_type(void); +int nostr_generate_nip42_challenge(char *challenge_out, size_t challenge_size, const char *client_ip); // NIP-42 function declarations void handle_auth_challenge_request(void); -int get_nip42_mode_config(void); - - - // Insert blob metadata into database -int insert_blob_metadata(const char* sha256, long size, const char* type, - long uploaded_at, const char* uploader_pubkey, - const char* filename) { - sqlite3* db; - sqlite3_stmt* stmt; - int rc; - - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL); - if (rc) { - fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); - return 0; - } - - const char* sql = "INSERT INTO blobs (sha256, size, type, uploaded_at, uploader_pubkey, filename) VALUES (?, ?, ?, ?, ?, ?)"; - - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { - fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); - sqlite3_close(db); - return 0; - } - - // Bind parameters - sqlite3_bind_text(stmt, 1, sha256, -1, SQLITE_STATIC); - sqlite3_bind_int64(stmt, 2, size); - sqlite3_bind_text(stmt, 3, type, -1, SQLITE_STATIC); - sqlite3_bind_int64(stmt, 4, uploaded_at); - if (uploader_pubkey) { - sqlite3_bind_text(stmt, 5, uploader_pubkey, -1, SQLITE_STATIC); - } else { - sqlite3_bind_null(stmt, 5); - } - if (filename) { - sqlite3_bind_text(stmt, 6, filename, -1, SQLITE_STATIC); - } else { - sqlite3_bind_null(stmt, 6); - } - - rc = sqlite3_step(stmt); - - int success = 0; - if (rc == SQLITE_DONE) { - success = 1; - } else if (rc == SQLITE_CONSTRAINT) { - // This is actually OK - blob already exists with same hash - success = 1; - } else { - success = 0; - } - - sqlite3_finalize(stmt); +int insert_blob_metadata(const char *sha256, long size, const char *type, + long uploaded_at, const char *uploader_pubkey, + const char *filename) { + sqlite3 *db; + sqlite3_stmt *stmt; + int rc; + + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL); + if (rc) { + fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); + return 0; + } + + const char *sql = "INSERT INTO blobs (sha256, size, type, uploaded_at, " + "uploader_pubkey, filename) VALUES (?, ?, ?, ?, ?, ?)"; + + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); sqlite3_close(db); - return success; + return 0; + } + + // Bind parameters + sqlite3_bind_text(stmt, 1, sha256, -1, SQLITE_STATIC); + sqlite3_bind_int64(stmt, 2, size); + sqlite3_bind_text(stmt, 3, type, -1, SQLITE_STATIC); + sqlite3_bind_int64(stmt, 4, uploaded_at); + if (uploader_pubkey) { + sqlite3_bind_text(stmt, 5, uploader_pubkey, -1, SQLITE_STATIC); + } else { + sqlite3_bind_null(stmt, 5); + } + if (filename) { + sqlite3_bind_text(stmt, 6, filename, -1, SQLITE_STATIC); + } else { + sqlite3_bind_null(stmt, 6); + } + + rc = sqlite3_step(stmt); + + int success = 0; + if (rc == SQLITE_DONE) { + success = 1; + } else if (rc == SQLITE_CONSTRAINT) { + // This is actually OK - blob already exists with same hash + success = 1; + } else { + success = 0; + } + + sqlite3_finalize(stmt); + sqlite3_close(db); + return success; } // Get blob metadata from database -int get_blob_metadata(const char* sha256, blob_metadata_t* metadata) { - sqlite3* db; - sqlite3_stmt* stmt; - int rc; - +int get_blob_metadata(const char *sha256, blob_metadata_t *metadata) { + sqlite3 *db; + sqlite3_stmt *stmt; + int rc; + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc) { + fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); + return 0; + } - - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc) { - fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); - return 0; - } - - const char* sql = "SELECT sha256, size, type, uploaded_at, filename FROM blobs WHERE sha256 = ?"; - - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { - fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); - sqlite3_close(db); - return 0; - } - - sqlite3_bind_text(stmt, 1, sha256, -1, SQLITE_STATIC); - - rc = sqlite3_step(stmt); - - if (rc == SQLITE_ROW) { - strncpy(metadata->sha256, (char*)sqlite3_column_text(stmt, 0), MAX_SHA256_LEN-1); - metadata->size = sqlite3_column_int64(stmt, 1); - strncpy(metadata->type, (char*)sqlite3_column_text(stmt, 2), MAX_MIME_LEN-1); - metadata->uploaded_at = sqlite3_column_int64(stmt, 3); - const char* filename = (char*)sqlite3_column_text(stmt, 4); - if (filename) { - strncpy(metadata->filename, filename, 255); - } else { - metadata->filename[0] = '\0'; - } - metadata->found = 1; - } else { - metadata->found = 0; - } - - sqlite3_finalize(stmt); + const char *sql = "SELECT sha256, size, type, uploaded_at, filename FROM " + "blobs WHERE sha256 = ?"; + + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db)); sqlite3_close(db); + return 0; + } - return metadata->found; + sqlite3_bind_text(stmt, 1, sha256, -1, SQLITE_STATIC); + + rc = sqlite3_step(stmt); + + if (rc == SQLITE_ROW) { + strncpy(metadata->sha256, (char *)sqlite3_column_text(stmt, 0), + MAX_SHA256_LEN - 1); + metadata->size = sqlite3_column_int64(stmt, 1); + strncpy(metadata->type, (char *)sqlite3_column_text(stmt, 2), + MAX_MIME_LEN - 1); + metadata->uploaded_at = sqlite3_column_int64(stmt, 3); + const char *filename = (char *)sqlite3_column_text(stmt, 4); + if (filename) { + strncpy(metadata->filename, filename, 255); + } else { + metadata->filename[0] = '\0'; + } + metadata->found = 1; + } else { + metadata->found = 0; + } + + sqlite3_finalize(stmt); + sqlite3_close(db); + + return metadata->found; } // Check if physical file exists (with extension based on MIME type) -int file_exists_with_type(const char* sha256, const char* mime_type) { - char filepath[MAX_PATH_LEN]; - const char* extension = mime_to_extension(mime_type); - - snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256, extension); - +int file_exists_with_type(const char *sha256, const char *mime_type) { + char filepath[MAX_PATH_LEN]; + const char *extension = mime_to_extension(mime_type); - - struct stat st; - int result = stat(filepath, &st); - - if (result == 0) { - return 1; - } else { - return 0; - } + snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256, extension); + + struct stat st; + int result = stat(filepath, &st); + + if (result == 0) { + return 1; + } else { + return 0; + } } // Handle HEAD request for blob -void handle_head_request(const char* sha256) { - blob_metadata_t metadata = {0}; - +void handle_head_request(const char *sha256) { + blob_metadata_t metadata = {0}; - - // Validate SHA-256 format (64 hex characters) - if (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 - this is the single source of truth - if (!get_blob_metadata(sha256, &metadata)) { - printf("Status: 404 Not Found\r\n"); - printf("Content-Type: text/plain\r\n\r\n"); - printf("Blob not found\n"); - return; - } - - // Return successful HEAD response with metadata from database - 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); - - // Add timing header for debugging - printf("X-Ginxsom-Server: FastCGI\r\n"); - printf("X-Ginxsom-Timestamp: %ld\r\n", time(NULL)); - - if (strlen(metadata.filename) > 0) { - printf("X-Original-Filename: %s\r\n", metadata.filename); - } - - printf("\r\n"); - // HEAD request - no body content + // Validate SHA-256 format (64 hex characters) + if (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 - this is the single source of truth + if (!get_blob_metadata(sha256, &metadata)) { + printf("Status: 404 Not Found\r\n"); + printf("Content-Type: text/plain\r\n\r\n"); + printf("Blob not found\n"); + return; + } + + // Return successful HEAD response with metadata from database + 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); + + // Add timing header for debugging + printf("X-Ginxsom-Server: FastCGI\r\n"); + printf("X-Ginxsom-Timestamp: %ld\r\n", time(NULL)); + + if (strlen(metadata.filename) > 0) { + printf("X-Original-Filename: %s\r\n", metadata.filename); + } + + printf("\r\n"); + // HEAD request - no body content } // Extract SHA-256 from request URI (Blossom compliant - ignores any extension) -const char* extract_sha256_from_uri(const char* uri) { - static char sha256_buffer[MAX_SHA256_LEN]; - - if (!uri || uri[0] != '/') { +const char *extract_sha256_from_uri(const char *uri) { + static char sha256_buffer[MAX_SHA256_LEN]; + + if (!uri || uri[0] != '/') { + return NULL; + } + + const char *start = uri + 1; // Skip leading '/' + + // Extract exactly 64 hex characters, ignoring anything after (extensions, + // etc.) + int len = 0; + for (int i = 0; i < 64 && start[i] != '\0'; i++) { + char c = start[i]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'))) { + // If we hit a non-hex character before 64 chars, it's invalid + if (len < 64) { return NULL; + } + break; } - - const char* start = uri + 1; // Skip leading '/' - - // Extract exactly 64 hex characters, ignoring anything after (extensions, etc.) - int len = 0; - for (int i = 0; i < 64 && start[i] != '\0'; i++) { - char c = start[i]; - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { - // If we hit a non-hex character before 64 chars, it's invalid - if (len < 64) { - return NULL; - } - break; - } - sha256_buffer[i] = c; - len = i + 1; - } - - // Must be exactly 64 hex characters - if (len != 64) { - return NULL; - } - - sha256_buffer[64] = '\0'; - return sha256_buffer; + sha256_buffer[i] = c; + len = i + 1; + } + + // Must be exactly 64 hex characters + if (len != 64) { + return NULL; + } + + sha256_buffer[64] = '\0'; + return sha256_buffer; } ///////////////////////////////////////////////////////////////////////////////////////// @@ -526,19 +513,11 @@ const char* extract_sha256_from_uri(const char* uri) { ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// -// NOTE: Old Blossom-specific authentication functions removed - now handled by nostr_core_lib - // Forward declarations for detailed validation functions -int detailed_structure_validation(cJSON* event); -int detailed_signature_validation(cJSON* event); -void analyze_event_fields(cJSON* event); -void hex_dump(const char* label, const unsigned char* data, size_t len); - -// Detailed structure validation function removed (debug version) - -// Debug functions removed (detailed_signature_validation, analyze_event_fields, hex_dump) - -// NOTE: authenticate_request() function removed - now using nostr_validate_request() from nostr_core_lib +int detailed_structure_validation(cJSON *event); +int detailed_signature_validation(cJSON *event); +void analyze_event_fields(cJSON *event); +void hex_dump(const char *label, const unsigned char *data, size_t len); ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// @@ -546,886 +525,638 @@ void hex_dump(const char* label, const unsigned char* data, size_t len); ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// -// Old authentication system has been replaced with nostr_core_lib unified request validation -// All authentication rules and cache functionality now handled by nostr_validate_request() - - - // Enhanced error response helper functions -void send_error_response(int status_code, const char* error_type, const char* message, const char* details) { - const char* status_text; - switch (status_code) { - case 400: status_text = "Bad Request"; break; - case 401: status_text = "Unauthorized"; break; - case 403: status_text = "Forbidden"; break; - case 409: status_text = "Conflict"; break; - case 413: status_text = "Payload Too Large"; break; - case 500: status_text = "Internal Server Error"; break; - default: status_text = "Error"; break; - } - - printf("Status: %d %s\r\n", status_code, status_text); - printf("Content-Type: application/json\r\n\r\n"); - printf("{\n"); - printf(" \"error\": \"%s\",\n", error_type); - printf(" \"message\": \"%s\"", message); - if (details) { - printf(",\n \"details\": \"%s\"", details); - } - printf("\n}\n"); +void send_error_response(int status_code, const char *error_type, + const char *message, const char *details) { + const char *status_text; + switch (status_code) { + case 400: + status_text = "Bad Request"; + break; + case 401: + status_text = "Unauthorized"; + break; + case 403: + status_text = "Forbidden"; + break; + case 409: + status_text = "Conflict"; + break; + case 413: + status_text = "Payload Too Large"; + break; + case 500: + status_text = "Internal Server Error"; + break; + default: + status_text = "Error"; + break; + } + + printf("Status: %d %s\r\n", status_code, status_text); + printf("Content-Type: application/json\r\n\r\n"); + printf("{\n"); + printf(" \"error\": \"%s\",\n", error_type); + printf(" \"message\": \"%s\"", message); + if (details) { + printf(",\n \"details\": \"%s\"", details); + } + printf("\n}\n"); } -void log_request(const char* method, const char* uri, const char* auth_status, int status_code) { - time_t now = time(NULL); - struct tm* tm_info = localtime(&now); - char timestamp[64]; - strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info); - - // For now, log to stdout - later can be configured to log files - fprintf(stderr, "LOG: [%s] %s %s - Auth: %s - Status: %d\r\n", - timestamp, method ? method : "NULL", uri ? uri : "NULL", - auth_status ? auth_status : "none", status_code); -} +void log_request(const char *method, const char *uri, const char *auth_status, + int status_code) { + time_t now = time(NULL); + struct tm *tm_info = localtime(&now); + char timestamp[64]; + strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info); + // For now, log to stdout - later can be configured to log files + fprintf(stderr, "LOG: [%s] %s %s - Auth: %s - Status: %d\r\n", timestamp, + method ? method : "NULL", uri ? uri : "NULL", + auth_status ? auth_status : "none", status_code); +} // Handle GET /list/ requests -void handle_list_request(const char* pubkey) { +void handle_list_request(const char *pubkey) { - - // Log the incoming request - log_request("GET", "/list", "pending", 0); - - // Validate pubkey format (64 hex characters) - if (!pubkey || strlen(pubkey) != 64) { - send_error_response(400, "invalid_pubkey", "Invalid pubkey format", "Pubkey must be 64 hex characters"); - log_request("GET", "/list", "none", 400); - return; + // Log the incoming request + log_request("GET", "/list", "pending", 0); + + // Validate pubkey format (64 hex characters) + if (!pubkey || strlen(pubkey) != 64) { + send_error_response(400, "invalid_pubkey", "Invalid pubkey format", + "Pubkey must be 64 hex characters"); + log_request("GET", "/list", "none", 400); + return; + } + + // Validate hex characters + for (int i = 0; i < 64; i++) { + char c = pubkey[i]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'))) { + send_error_response(400, "invalid_pubkey", "Invalid pubkey format", + "Pubkey must contain only hex characters"); + log_request("GET", "/list", "none", 400); + return; } - - // Validate hex characters - for (int i = 0; i < 64; i++) { - char c = pubkey[i]; - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { - send_error_response(400, "invalid_pubkey", "Invalid pubkey format", "Pubkey must contain only hex characters"); - log_request("GET", "/list", "none", 400); - return; - } + } + + // Get query parameters for since/until filtering + const char *query_string = getenv("QUERY_STRING"); + long since_timestamp = 0; + long until_timestamp = 0; + + if (query_string) { + + // Parse since parameter + const char *since_param = strstr(query_string, "since="); + if (since_param) { + since_timestamp = atol(since_param + 6); } - - // Get query parameters for since/until filtering - const char* query_string = getenv("QUERY_STRING"); - long since_timestamp = 0; - long until_timestamp = 0; - - if (query_string) { - - // Parse since parameter - const char* since_param = strstr(query_string, "since="); - if (since_param) { - since_timestamp = atol(since_param + 6); - - } - - // Parse until parameter - const char* until_param = strstr(query_string, "until="); - if (until_param) { - until_timestamp = atol(until_param + 6); - - } + // Parse until parameter + const char *until_param = strstr(query_string, "until="); + if (until_param) { + until_timestamp = atol(until_param + 6); } - - // Check for optional authorization - const char* auth_header = getenv("HTTP_AUTHORIZATION"); - const char* auth_status = "none"; - - if (auth_header) { + } - nostr_request_t request = { - .operation = "list", - .auth_header = auth_header, - .event = NULL, - .resource_hash = NULL, - .mime_type = NULL, - .file_size = 0, - .client_ip = getenv("REMOTE_ADDR"), - .app_context = NULL - }; - - nostr_request_result_t result; - int auth_result = nostr_validate_request(&request, &result); - - if (auth_result != NOSTR_SUCCESS || !result.valid) { - const char* violation_type = nostr_request_validator_get_last_violation_type(); + // Authentication is handled by centralized validation system + // Check if auth header was provided to set appropriate status + const char *auth_header = getenv("HTTP_AUTHORIZATION"); + const char *auth_status = auth_header ? "authenticated" : "none"; - const char* error_type = "authentication_failed"; - const char* message = "Invalid or expired authentication"; - const char* details = result.reason[0] ? result.reason : "The provided Nostr event is invalid, expired, or does not authorize this operation"; - int status_code = 401; // Default to 401 for authentication issues + // Query database for blobs uploaded by this pubkey + sqlite3 *db; + sqlite3_stmt *stmt; + int rc; - // Determine status code and error type based on violation type - if (strcmp(violation_type, "pubkey_blacklist") == 0) { - error_type = "access_denied"; - message = "Access denied by policy"; - details = "Public key blacklisted"; - status_code = 403; // Access control policy denial - } else if (strcmp(violation_type, "hash_blacklist") == 0) { - error_type = "access_denied"; - message = "Access denied by policy"; - details = "File hash blacklisted"; - status_code = 403; // Access control policy denial - } else if (strcmp(violation_type, "whitelist_violation") == 0) { - error_type = "pubkey_not_whitelisted"; - message = "Public key not authorized"; - details = "Public key not whitelisted for this operation"; - status_code = 403; // Access control policy denial - } else if (strstr(result.reason, "whitelist")) { - error_type = "pubkey_not_whitelisted"; - message = "Public key not authorized"; - status_code = 403; // Access control policy denial - } else if (strstr(result.reason, "blacklist")) { - error_type = "access_denied"; - message = "Access denied by policy"; - status_code = 403; // Access control policy denial - } + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc) { - send_error_response(status_code, error_type, message, details); - log_request("GET", "/list", "failed", status_code); - return; - } - auth_status = "authenticated"; - } - - // Query database for blobs uploaded by this pubkey - sqlite3* db; - sqlite3_stmt* stmt; - int rc; - - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc) { + send_error_response(500, "database_error", "Failed to access database", + "Internal server error"); + log_request("GET", "/list", auth_status, 500); + return; + } - send_error_response(500, "database_error", "Failed to access database", "Internal server error"); - log_request("GET", "/list", auth_status, 500); - return; - } - - // Build SQL query with optional timestamp filtering - char sql[1024]; - if (since_timestamp > 0 && until_timestamp > 0) { - snprintf(sql, sizeof(sql), - "SELECT sha256, size, type, uploaded_at, filename FROM blobs WHERE uploader_pubkey = ? AND uploaded_at >= ? AND uploaded_at <= ? ORDER BY uploaded_at DESC"); - } else if (since_timestamp > 0) { - snprintf(sql, sizeof(sql), - "SELECT sha256, size, type, uploaded_at, filename FROM blobs WHERE uploader_pubkey = ? AND uploaded_at >= ? ORDER BY uploaded_at DESC"); - } else if (until_timestamp > 0) { - snprintf(sql, sizeof(sql), - "SELECT sha256, size, type, uploaded_at, filename FROM blobs WHERE uploader_pubkey = ? AND uploaded_at <= ? ORDER BY uploaded_at DESC"); - } else { - snprintf(sql, sizeof(sql), - "SELECT sha256, size, type, uploaded_at, filename FROM blobs WHERE uploader_pubkey = ? ORDER BY uploaded_at DESC"); - } - + // Build SQL query with optional timestamp filtering + char sql[1024]; + if (since_timestamp > 0 && until_timestamp > 0) { + snprintf(sql, sizeof(sql), + "SELECT sha256, size, type, uploaded_at, filename FROM blobs " + "WHERE uploader_pubkey = ? AND uploaded_at >= ? AND uploaded_at " + "<= ? ORDER BY uploaded_at DESC"); + } else if (since_timestamp > 0) { + snprintf( + sql, sizeof(sql), + "SELECT sha256, size, type, uploaded_at, filename FROM blobs WHERE " + "uploader_pubkey = ? AND uploaded_at >= ? ORDER BY uploaded_at DESC"); + } else if (until_timestamp > 0) { + snprintf( + sql, sizeof(sql), + "SELECT sha256, size, type, uploaded_at, filename FROM blobs WHERE " + "uploader_pubkey = ? AND uploaded_at <= ? ORDER BY uploaded_at DESC"); + } else { + snprintf(sql, sizeof(sql), + "SELECT sha256, size, type, uploaded_at, filename FROM blobs " + "WHERE uploader_pubkey = ? ORDER BY uploaded_at DESC"); + } - - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { - sqlite3_close(db); - send_error_response(500, "database_error", "Failed to prepare query", "Internal server error"); - log_request("GET", "/list", auth_status, 500); - return; - } - - // Bind parameters - sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC); - int param_index = 2; - - if (since_timestamp > 0) { - sqlite3_bind_int64(stmt, param_index++, since_timestamp); - } - if (until_timestamp > 0) { - sqlite3_bind_int64(stmt, param_index++, until_timestamp); - } - - // Start JSON response - printf("Status: 200 OK\r\n"); - printf("Content-Type: application/json\r\n\r\n"); - printf("[\n"); - - int first_item = 1; - while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { - if (!first_item) { - printf(",\n"); - } - first_item = 0; - - const char* sha256 = (const char*)sqlite3_column_text(stmt, 0); - long size = sqlite3_column_int64(stmt, 1); - const char* type = (const char*)sqlite3_column_text(stmt, 2); - long uploaded_at = sqlite3_column_int64(stmt, 3); - const char* filename = (const char*)sqlite3_column_text(stmt, 4); - - // Get origin from config for consistent URL generation - char origin[256]; - nip94_get_origin(origin, sizeof(origin)); - - // Build canonical blob URL using centralized function - char blob_url[512]; - nip94_build_blob_url(origin, sha256, type, blob_url, sizeof(blob_url)); - - // Output blob descriptor JSON - printf(" {\n"); - printf(" \"url\": \"%s\",\n", blob_url); - printf(" \"sha256\": \"%s\",\n", sha256); - printf(" \"size\": %ld,\n", size); - printf(" \"type\": \"%s\",\n", type); - printf(" \"uploaded\": %ld", uploaded_at); - - // Add optional filename if available - if (filename && strlen(filename) > 0) { - printf(",\n \"filename\": \"%s\"", filename); - } - - printf("\n }"); - } - - printf("\n]\n"); - - sqlite3_finalize(stmt); sqlite3_close(db); - + send_error_response(500, "database_error", "Failed to prepare query", + "Internal server error"); + log_request("GET", "/list", auth_status, 500); + return; + } - log_request("GET", "/list", auth_status, 200); + // Bind parameters + sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC); + int param_index = 2; + + if (since_timestamp > 0) { + sqlite3_bind_int64(stmt, param_index++, since_timestamp); + } + if (until_timestamp > 0) { + sqlite3_bind_int64(stmt, param_index++, until_timestamp); + } + + // Start JSON response + printf("Status: 200 OK\r\n"); + printf("Content-Type: application/json\r\n\r\n"); + printf("[\n"); + + int first_item = 1; + while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + if (!first_item) { + printf(",\n"); + } + first_item = 0; + + const char *sha256 = (const char *)sqlite3_column_text(stmt, 0); + long size = sqlite3_column_int64(stmt, 1); + const char *type = (const char *)sqlite3_column_text(stmt, 2); + long uploaded_at = sqlite3_column_int64(stmt, 3); + const char *filename = (const char *)sqlite3_column_text(stmt, 4); + + // Get origin from config for consistent URL generation + char origin[256]; + nip94_get_origin(origin, sizeof(origin)); + + // Build canonical blob URL using centralized function + char blob_url[512]; + nip94_build_blob_url(origin, sha256, type, blob_url, sizeof(blob_url)); + + // Output blob descriptor JSON + printf(" {\n"); + printf(" \"url\": \"%s\",\n", blob_url); + printf(" \"sha256\": \"%s\",\n", sha256); + printf(" \"size\": %ld,\n", size); + printf(" \"type\": \"%s\",\n", type); + printf(" \"uploaded\": %ld", uploaded_at); + + // Add optional filename if available + if (filename && strlen(filename) > 0) { + printf(",\n \"filename\": \"%s\"", filename); + } + + printf("\n }"); + } + + printf("\n]\n"); + + sqlite3_finalize(stmt); + sqlite3_close(db); + + log_request("GET", "/list", auth_status, 200); } // Handle DELETE / requests -void handle_delete_request(const char* sha256) { +void handle_delete_request(const char *sha256) { - - // Log the incoming request - log_request("DELETE", "/delete", "pending", 0); - - // Validate SHA-256 format (64 hex characters) - if (!sha256 || strlen(sha256) != 64) { - send_error_response(400, "invalid_hash", "Invalid SHA-256 hash format", "Hash must be 64 hex characters"); - log_request("DELETE", "/delete", "none", 400); - return; - } - - // Validate hex characters - for (int i = 0; i < 64; i++) { - char c = sha256[i]; - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { - send_error_response(400, "invalid_hash", "Invalid SHA-256 hash format", "Hash must contain only hex characters"); - log_request("DELETE", "/delete", "none", 400); - return; - } - } - - // Require authorization for delete operations - const char* auth_header = getenv("HTTP_AUTHORIZATION"); - if (!auth_header) { - send_error_response(401, "authorization_required", "Authorization required for delete operations", - "Delete operations require a valid Nostr authorization event"); - log_request("DELETE", "/delete", "missing_auth", 401); - return; - } - - // Authenticate the request with enhanced rules system - nostr_request_t request = { - .operation = "delete", - .auth_header = auth_header, - .event = NULL, - .resource_hash = sha256, - .mime_type = NULL, - .file_size = 0, - .client_ip = getenv("REMOTE_ADDR"), - .app_context = NULL - }; - - // Debug: Print environment variable status - char* debug_flag = getenv("GINX_DEBUG"); - fprintf(stderr, "AUTH DEBUG: GINX_DEBUG=%s\n", debug_flag ? debug_flag : "NOT_SET"); + // Log the incoming request + log_request("DELETE", "/delete", "pending", 0); - nostr_request_result_t result; - int auth_result = nostr_validate_request(&request, &result); + // Validate SHA-256 format (64 hex characters) + if (!sha256 || strlen(sha256) != 64) { + send_error_response(400, "invalid_hash", "Invalid SHA-256 hash format", + "Hash must be 64 hex characters"); + log_request("DELETE", "/delete", "none", 400); + return; + } - // Debug: Print auth result immediately after call - fprintf(stderr, "AUTH DEBUG: handle_upload_request - nostr_validate_request returned: %d, result.valid: %d\n", auth_result, result.valid); - - if (auth_result != NOSTR_SUCCESS || !result.valid) { - const char* violation_type = nostr_request_validator_get_last_violation_type(); + // Validate hex characters + for (int i = 0; i < 64; i++) { + char c = sha256[i]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'))) { + send_error_response(400, "invalid_hash", "Invalid SHA-256 hash format", + "Hash must contain only hex characters"); + log_request("DELETE", "/delete", "none", 400); + return; + } + } - const char* error_type = "authentication_failed"; - const char* message = "Invalid or expired authentication"; - const char* details = result.reason[0] ? result.reason : "The provided Nostr event is invalid, expired, or does not authorize this operation"; - int status_code = 401; // Default to 401 for authentication issues + // Authentication is handled by centralized validation system + // TODO: Get auth_pubkey from centralized validation result for ownership check + // For now, temporarily disable ownership validation until we pass validation results + const char *auth_pubkey = "placeholder"; // This will be passed from centralized validation - // Determine status code and error type based on violation type - if (strcmp(violation_type, "pubkey_blacklist") == 0) { - error_type = "access_denied"; - message = "Access denied by policy"; - details = "Public key blacklisted"; - status_code = 403; // Access control policy denial - } else if (strcmp(violation_type, "hash_blacklist") == 0) { - error_type = "access_denied"; - message = "Access denied by policy"; - details = "File hash blacklisted"; - status_code = 403; // Access control policy denial - } else if (strcmp(violation_type, "whitelist_violation") == 0) { - error_type = "pubkey_not_whitelisted"; - message = "Public key not authorized"; - details = "Public key not whitelisted for this operation"; - status_code = 403; // Access control policy denial - } else if (strstr(result.reason, "whitelist")) { - error_type = "pubkey_not_whitelisted"; - message = "Public key not authorized"; - status_code = 403; // Access control policy denial - } else if (strstr(result.reason, "blacklist")) { - error_type = "access_denied"; - message = "Access denied by policy"; - status_code = 403; // Access control policy denial - } + // Check if blob exists in database + sqlite3 *db; + sqlite3_stmt *stmt; + int rc; - send_error_response(status_code, error_type, message, details); - log_request("DELETE", "/delete", "failed", status_code); - return; - } - - // Extract pubkey from validation result for ownership check - const char* auth_pubkey = result.pubkey[0] ? result.pubkey : NULL; - if (!auth_pubkey) { - send_error_response(401, "authentication_failed", "Missing pubkey in authorization", - "The provided authorization does not contain a valid pubkey"); - log_request("DELETE", "/delete", "missing_pubkey", 401); - return; - } - - // Check if blob exists in database - sqlite3* db; - sqlite3_stmt* stmt; - int rc; - - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL); - if (rc) { + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL); + if (rc) { - send_error_response(500, "database_error", "Failed to access database", "Internal server error"); - log_request("DELETE", "/delete", "authenticated", 500); - return; - } - - // Query blob metadata and check ownership - const char* sql = "SELECT uploader_pubkey, type FROM blobs WHERE sha256 = ?"; - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { + send_error_response(500, "database_error", "Failed to access database", + "Internal server error"); + log_request("DELETE", "/delete", "authenticated", 500); + return; + } - sqlite3_close(db); - send_error_response(500, "database_error", "Failed to prepare query", "Internal server error"); - log_request("DELETE", "/delete", "authenticated", 500); - return; - } - - sqlite3_bind_text(stmt, 1, sha256, -1, SQLITE_STATIC); - - rc = sqlite3_step(stmt); - if (rc != SQLITE_ROW) { - sqlite3_finalize(stmt); - sqlite3_close(db); - send_error_response(404, "blob_not_found", "Blob not found", "The specified blob does not exist"); - log_request("DELETE", "/delete", "authenticated", 404); - return; - } - - // Get blob metadata - const char* uploader_pubkey = (const char*)sqlite3_column_text(stmt, 0); - const char* blob_type = (const char*)sqlite3_column_text(stmt, 1); + // Query blob metadata and check ownership + const char *sql = "SELECT uploader_pubkey, type FROM blobs WHERE sha256 = ?"; + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { - - // Create copies of the strings since they may be invalidated after finalize - char uploader_pubkey_copy[256] = {0}; - char blob_type_copy[128] = {0}; - - if (uploader_pubkey) { - strncpy(uploader_pubkey_copy, uploader_pubkey, sizeof(uploader_pubkey_copy) - 1); - } - if (blob_type) { - strncpy(blob_type_copy, blob_type, sizeof(blob_type_copy) - 1); - } - - sqlite3_finalize(stmt); - - - // Check ownership - only the uploader can delete -if (!uploader_pubkey_copy[0] || strcmp(uploader_pubkey_copy, auth_pubkey) != 0) { - sqlite3_close(db); - send_error_response(403, "access_denied", "Access denied", "You can only delete blobs that you uploaded"); - log_request("DELETE", "/delete", "ownership_denied", 403); - return; - } else { - } - + sqlite3_close(db); + send_error_response(500, "database_error", "Failed to prepare query", + "Internal server error"); + log_request("DELETE", "/delete", "authenticated", 500); + return; + } - - // Delete from database first - const char* delete_sql = "DELETE FROM blobs WHERE sha256 = ?"; - rc = sqlite3_prepare_v2(db, delete_sql, -1, &stmt, NULL); - if (rc != SQLITE_OK) { + sqlite3_bind_text(stmt, 1, sha256, -1, SQLITE_STATIC); - sqlite3_close(db); - send_error_response(500, "database_error", "Failed to prepare delete", "Internal server error"); - log_request("DELETE", "/delete", "authenticated", 500); - return; - } - - sqlite3_bind_text(stmt, 1, sha256, -1, SQLITE_STATIC); - - rc = sqlite3_step(stmt); + rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW) { sqlite3_finalize(stmt); sqlite3_close(db); - - if (rc != SQLITE_DONE) { + send_error_response(404, "blob_not_found", "Blob not found", + "The specified blob does not exist"); + log_request("DELETE", "/delete", "authenticated", 404); + return; + } - send_error_response(500, "database_error", "Failed to delete blob metadata", "Internal server error"); - log_request("DELETE", "/delete", "authenticated", 500); - return; - } - + // Get blob metadata + const char *uploader_pubkey = (const char *)sqlite3_column_text(stmt, 0); + const char *blob_type = (const char *)sqlite3_column_text(stmt, 1); - - // Determine file extension from MIME type and delete physical file - const char* extension = ""; - if (strstr(blob_type_copy, "image/jpeg")) { - extension = ".jpg"; - } else if (strstr(blob_type_copy, "image/webp")) { - extension = ".webp"; - } else if (strstr(blob_type_copy, "image/png")) { - extension = ".png"; - } else if (strstr(blob_type_copy, "image/gif")) { - extension = ".gif"; - } else if (strstr(blob_type_copy, "video/mp4")) { - extension = ".mp4"; - } else if (strstr(blob_type_copy, "video/webm")) { - extension = ".webm"; - } else if (strstr(blob_type_copy, "audio/mpeg")) { - extension = ".mp3"; - } else if (strstr(blob_type_copy, "audio/ogg")) { - extension = ".ogg"; - } else if (strstr(blob_type_copy, "text/plain")) { - extension = ".txt"; - } else { - extension = ".bin"; - } - - char filepath[MAX_PATH_LEN]; - snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256, extension); - - // Delete the physical file - if (unlink(filepath) != 0) { - // Warning: failed to delete physical file - } else { - // Successfully deleted physical file - } + // Create copies of the strings since they may be invalidated after finalize + char uploader_pubkey_copy[256] = {0}; + char blob_type_copy[128] = {0}; + if (uploader_pubkey) { + strncpy(uploader_pubkey_copy, uploader_pubkey, + sizeof(uploader_pubkey_copy) - 1); + } + if (blob_type) { + strncpy(blob_type_copy, blob_type, sizeof(blob_type_copy) - 1); + } - + sqlite3_finalize(stmt); - // Return success response - printf("Status: 200 OK\r\n"); - printf("Content-Type: application/json\r\n\r\n"); - printf("{\n"); - printf(" \"message\": \"Blob deleted successfully\",\n"); - printf(" \"sha256\": \"%s\"\n", sha256); - printf("}\n"); + // Check ownership - only the uploader can delete + if (!uploader_pubkey_copy[0] || + strcmp(uploader_pubkey_copy, auth_pubkey) != 0) { + sqlite3_close(db); + send_error_response(403, "access_denied", "Access denied", + "You can only delete blobs that you uploaded"); + log_request("DELETE", "/delete", "ownership_denied", 403); + return; + } else { + } - log_request("DELETE", "/delete", "authenticated", 200); + // Delete from database first + const char *delete_sql = "DELETE FROM blobs WHERE sha256 = ?"; + rc = sqlite3_prepare_v2(db, delete_sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + + sqlite3_close(db); + send_error_response(500, "database_error", "Failed to prepare delete", + "Internal server error"); + log_request("DELETE", "/delete", "authenticated", 500); + return; + } + + sqlite3_bind_text(stmt, 1, sha256, -1, SQLITE_STATIC); + + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + sqlite3_close(db); + + if (rc != SQLITE_DONE) { + + send_error_response(500, "database_error", "Failed to delete blob metadata", + "Internal server error"); + log_request("DELETE", "/delete", "authenticated", 500); + return; + } + + // Determine file extension from MIME type and delete physical file + const char *extension = ""; + if (strstr(blob_type_copy, "image/jpeg")) { + extension = ".jpg"; + } else if (strstr(blob_type_copy, "image/webp")) { + extension = ".webp"; + } else if (strstr(blob_type_copy, "image/png")) { + extension = ".png"; + } else if (strstr(blob_type_copy, "image/gif")) { + extension = ".gif"; + } else if (strstr(blob_type_copy, "video/mp4")) { + extension = ".mp4"; + } else if (strstr(blob_type_copy, "video/webm")) { + extension = ".webm"; + } else if (strstr(blob_type_copy, "audio/mpeg")) { + extension = ".mp3"; + } else if (strstr(blob_type_copy, "audio/ogg")) { + extension = ".ogg"; + } else if (strstr(blob_type_copy, "text/plain")) { + extension = ".txt"; + } else { + extension = ".bin"; + } + + char filepath[MAX_PATH_LEN]; + snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256, extension); + + // Delete the physical file + if (unlink(filepath) != 0) { + // Warning: failed to delete physical file + } else { + // Successfully deleted physical file + } + + // Return success response + printf("Status: 200 OK\r\n"); + printf("Content-Type: application/json\r\n\r\n"); + printf("{\n"); + printf(" \"message\": \"Blob deleted successfully\",\n"); + printf(" \"sha256\": \"%s\"\n", sha256); + printf("}\n"); + + log_request("DELETE", "/delete", "authenticated", 200); } // Handle PUT /upload requests void handle_upload_request(void) { - - // Log the incoming request - log_request("PUT", "/upload", "pending", 0); - - // Get HTTP headers - const char* content_type = getenv("CONTENT_TYPE"); - const char* content_length_str = getenv("CONTENT_LENGTH"); - + // Log the incoming request + log_request("PUT", "/upload", "pending", 0); - - // Validate required headers - if (!content_type) { - send_error_response(400, "missing_header", "Content-Type header required", "The Content-Type header must be specified for file uploads"); - log_request("PUT", "/upload", "none", 400); - return; - } - - if (!content_length_str) { - send_error_response(400, "missing_header", "Content-Length header required", "The Content-Length header must be specified for file uploads"); - log_request("PUT", "/upload", "none", 400); - return; - } - - long content_length = atol(content_length_str); - if (content_length <= 0 || content_length > 100 * 1024 * 1024) { // 100MB limit - send_error_response(413, "payload_too_large", "File size must be between 1 byte and 100MB", "Maximum allowed file size is 100MB"); - log_request("PUT", "/upload", "none", 413); - return; - } - - // Get Authorization header for authentication - const char* auth_header = getenv("HTTP_AUTHORIZATION"); + // Get HTTP headers + const char *content_type = getenv("CONTENT_TYPE"); + const char *content_length_str = getenv("CONTENT_LENGTH"); - - // Store uploader pubkey for metadata (will be extracted during auth if provided) - const char* uploader_pubkey = NULL; - if (auth_header) { - log_request("PUT", "/upload", "auth_provided", 0); - } else { - log_request("PUT", "/upload", "anonymous", 0); - } - - // Read file data from stdin - unsigned char* file_data = malloc(content_length); - if (!file_data) { - printf("Status: 500 Internal Server Error\r\n"); - printf("Content-Type: text/plain\r\n\r\n"); - printf("Memory allocation failed\n"); - return; - } - - size_t bytes_read = fread(file_data, 1, content_length, stdin); - if (bytes_read != (size_t)content_length) { + // Validate required headers + if (!content_type) { + send_error_response( + 400, "missing_header", "Content-Type header required", + "The Content-Type header must be specified for file uploads"); + log_request("PUT", "/upload", "none", 400); + return; + } - free(file_data); - printf("Status: 400 Bad Request\r\n"); - printf("Content-Type: text/plain\r\n\r\n"); - printf("Failed to read complete file data\n"); - return; - } + if (!content_length_str) { + send_error_response( + 400, "missing_header", "Content-Length header required", + "The Content-Length header must be specified for file uploads"); + log_request("PUT", "/upload", "none", 400); + return; + } - // Calculate SHA-256 hash using nostr_core function - unsigned char hash[32]; + long content_length = atol(content_length_str); + if (content_length <= 0 || + content_length > 100 * 1024 * 1024) { // 100MB limit + send_error_response(413, "payload_too_large", + "File size must be between 1 byte and 100MB", + "Maximum allowed file size is 100MB"); + log_request("PUT", "/upload", "none", 413); + return; + } + // Get Authorization header for authentication + const char *auth_header = getenv("HTTP_AUTHORIZATION"); - - if (nostr_sha256(file_data, content_length, hash) != NOSTR_SUCCESS) { - free(file_data); - printf("Status: 500 Internal Server Error\r\n"); - printf("Content-Type: text/plain\r\n\r\n"); - printf("Hash calculation failed\n"); - return; - } - - // Convert hash to hex string - char sha256_hex[65]; - nostr_bytes_to_hex(hash, 32, sha256_hex); + // Store uploader pubkey for metadata (will be extracted during auth if + // provided) + const char *uploader_pubkey = NULL; + if (auth_header) { + log_request("PUT", "/upload", "auth_provided", 0); + } else { + log_request("PUT", "/upload", "anonymous", 0); + } - fflush(stderr); - + // Read file data from stdin + unsigned char *file_data = malloc(content_length); + if (!file_data) { + printf("Status: 500 Internal Server Error\r\n"); + printf("Content-Type: text/plain\r\n\r\n"); + printf("Memory allocation failed\n"); + return; + } - - // Check if authentication rules are enabled using nostr_core_lib system - int auth_required = nostr_auth_rules_enabled(); - fprintf(stderr, "AUTH: auth_rules_enabled = %d, auth_header present: %s\r\n", auth_required, auth_header ? "YES" : "NO"); + size_t bytes_read = fread(file_data, 1, content_length, stdin); + if (bytes_read != (size_t)content_length) { - // If authentication is required but no auth header provided, fail immediately - if (auth_required && !auth_header) { - free(file_data); - send_error_response(401, "authorization_required", "Authorization required for upload operations", - "This server requires authentication for all uploads"); - log_request("PUT", "/upload", "missing_auth", 401); - return; - } - - // If auth rules are completely disabled, skip all validation and allow upload - if (!auth_required) { - fprintf(stderr, "AUTH: Authentication rules disabled - skipping all validation and allowing upload\n"); - // Skip validation and proceed to file processing - goto process_file_upload; - } - - // Use new unified request validation system (only when auth is required) - fprintf(stderr, "AUTH: About to perform authentication validation\r\n"); - - // Create request structure for validation - nostr_request_t request = { - .operation = "upload", - .auth_header = auth_header, - .event = NULL, - .resource_hash = sha256_hex, - .mime_type = content_type, - .file_size = content_length, - .client_ip = getenv("REMOTE_ADDR"), - .app_context = NULL - }; - - nostr_request_result_t result; - fprintf(stderr, "UPLOAD_HANDLER: About to call nostr_validate_request for operation='%s'\r\n", request.operation ? request.operation : "NULL"); - int auth_result = nostr_validate_request(&request, &result); - fprintf(stderr, "UPLOAD_HANDLER: nostr_validate_request returned auth_result=%d, result.valid=%d\r\n", auth_result, result.valid); - - // Write debug output to a file for debugging - FILE* debug_file = fopen("debug_auth.log", "a"); - if (debug_file) { - fprintf(debug_file, "AUTH: nostr_validate_request returned: %d, valid: %d, reason: %s\n", - auth_result, result.valid, result.reason); - - // Validate pubkey before printing to prevent corruption display - if (result.pubkey[0] != '\0' && strlen(result.pubkey) == 64) { - // Additional validation: ensure pubkey contains only hex characters - int valid_hex = 1; - for (int i = 0; i < 64; i++) { - char c = result.pubkey[i]; - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { - valid_hex = 0; - break; - } - } - if (valid_hex) { - fprintf(debug_file, "AUTH: pubkey extracted: '%s'\n", result.pubkey); - } else { - fprintf(debug_file, "AUTH: pubkey extracted: \n"); - } - } else { - fprintf(debug_file, "AUTH: pubkey extracted: \n"); - } - - fprintf(debug_file, "AUTH: resource_hash: '%s'\n", request.resource_hash ? request.resource_hash : "NULL"); - fprintf(debug_file, "AUTH: operation: '%s'\n", request.operation ? request.operation : "NULL"); - fprintf(debug_file, "AUTH: auth_header present: %s\n", auth_header ? "YES" : "NO"); - fclose(debug_file); - } - - // Authentication failed - reject the request - if (auth_result != NOSTR_SUCCESS || !result.valid) { - free(file_data); - - // Get violation type from validator for precise status code mapping - const char* violation_type = nostr_request_validator_get_last_violation_type(); - - // Use the detailed reason from the authentication system - const char* error_type = "authentication_failed"; - const char* message = "Authentication failed"; - const char* details = result.reason[0] ? result.reason : "The request failed authentication"; - int status_code = 401; // Default to 401 for authentication issues - - // Determine status code and error type based on violation type - if (strcmp(violation_type, "pubkey_blacklist") == 0) { - error_type = "access_denied"; - message = "Access denied by policy"; - details = "Public key blacklisted"; - status_code = 403; // Access control policy denial - } else if (strcmp(violation_type, "hash_blacklist") == 0) { - error_type = "access_denied"; - message = "Access denied by policy"; - details = "File hash blacklisted"; - status_code = 403; // Access control policy denial - } else if (strcmp(violation_type, "whitelist_violation") == 0) { - error_type = "pubkey_not_whitelisted"; - message = "Public key not authorized"; - details = "Public key not whitelisted for this operation"; - status_code = 403; // Access control policy denial - } else if (strstr(result.reason, "whitelist")) { - error_type = "pubkey_not_whitelisted"; - message = "Public key not authorized"; - status_code = 403; // Access control policy denial - } else if (strstr(result.reason, "blacklist")) { - error_type = "access_denied"; - message = "Access denied by policy"; - status_code = 403; // Access control policy denial - } else if (strstr(result.reason, "expired")) { - error_type = "event_expired"; - message = "Authentication event expired"; - status_code = 401; // Authentication format/validity issue - } else if (strstr(result.reason, "signature")) { - error_type = "invalid_signature"; - message = "Invalid cryptographic signature"; - status_code = 401; // Authentication format/validity issue - } else if (strstr(result.reason, "size")) { - error_type = "file_too_large"; - message = "File size exceeds policy limits"; - status_code = 403; // Access control policy denial - } else if (strstr(result.reason, "MIME") || strstr(result.reason, "mime")) { - error_type = "unsupported_type"; - message = "File type not allowed by policy"; - status_code = 403; // Access control policy denial - } else if (strstr(result.reason, "hash")) { - error_type = "hash_blocked"; - message = "File hash blocked by policy"; - status_code = 403; // Access control policy denial - } else if (strstr(result.reason, "format") || strstr(result.reason, "invalid")) { - error_type = "invalid_format"; - message = "Invalid authorization format"; - status_code = 401; // Authentication format/validity issue - } - - send_error_response(status_code, error_type, message, details); - log_request("PUT", "/upload", "auth_failed", status_code); - return; - } - - // Extract uploader pubkey from validation result if auth was provided - if (auth_header && result.pubkey[0]) { - static char pubkey_buffer[256]; - strncpy(pubkey_buffer, result.pubkey, sizeof(pubkey_buffer)-1); - pubkey_buffer[sizeof(pubkey_buffer)-1] = '\0'; - uploader_pubkey = pubkey_buffer; - } - - - - process_file_upload: - // Get dimensions from in-memory buffer before saving file - int width = 0, height = 0; - nip94_get_dimensions(file_data, content_length, content_type, &width, &height); - - // Determine file extension from Content-Type using centralized mapping - const char* extension = mime_to_extension(content_type); - - // Save file to blobs directory with SHA-256 + extension - char filepath[MAX_PATH_LEN]; - snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256_hex, extension); - - - - FILE* outfile = fopen(filepath, "wb"); - if (!outfile) { - free(file_data); - printf("Status: 500 Internal Server Error\r\n"); - printf("Content-Type: text/plain\r\n\r\n"); - printf("Failed to create file\n"); - return; - } - - size_t bytes_written = fwrite(file_data, 1, content_length, outfile); - fclose(outfile); - - // Set file permissions to 644 (owner read/write, group/others read) - standard for web files - if (chmod(filepath, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) != 0) { - fprintf(stderr, "WARNING: Failed to set file permissions for %s\r\n", filepath); - // Continue anyway - this is not a fatal error - } else { - - } free(file_data); - - if (bytes_written != (size_t)content_length) { - // Clean up partial file - unlink(filepath); - printf("Status: 500 Internal Server Error\r\n"); - printf("Content-Type: text/plain\r\n\r\n"); - printf("Failed to write complete file\n"); - return; - } - + printf("Status: 400 Bad Request\r\n"); + printf("Content-Type: text/plain\r\n\r\n"); + printf("Failed to read complete file data\n"); + return; + } - - // Extract filename from Content-Disposition header if present - const char* filename = NULL; - const char* content_disposition = getenv("HTTP_CONTENT_DISPOSITION"); + // Calculate SHA-256 hash using nostr_core function + unsigned char hash[32]; - - if (content_disposition) { + if (nostr_sha256(file_data, content_length, hash) != NOSTR_SUCCESS) { + free(file_data); + printf("Status: 500 Internal Server Error\r\n"); + printf("Content-Type: text/plain\r\n\r\n"); + printf("Hash calculation failed\n"); + return; + } - // Look for filename= in Content-Disposition header - const char* filename_start = strstr(content_disposition, "filename="); - if (filename_start) { + // Convert hash to hex string + char sha256_hex[65]; + nostr_bytes_to_hex(hash, 32, sha256_hex); - filename_start += 9; // Skip "filename=" + fflush(stderr); - - // Handle quoted filenames - if (*filename_start == '"') { + // Check if authentication rules are enabled using nostr_core_lib system + int auth_required = nostr_auth_rules_enabled(); + fprintf(stderr, "AUTH: auth_rules_enabled = %d, auth_header present: %s\r\n", + auth_required, auth_header ? "YES" : "NO"); - filename_start++; // Skip opening quote - // Find closing quote - const char* filename_end = strchr(filename_start, '"'); - if (filename_end) { - // Extract filename between quotes - static char filename_buffer[256]; - size_t filename_len = filename_end - filename_start; + // If authentication is required but no auth header provided, fail immediately + if (auth_required && !auth_header) { + free(file_data); + send_error_response(401, "authorization_required", + "Authorization required for upload operations", + "This server requires authentication for all uploads"); + log_request("PUT", "/upload", "missing_auth", 401); + return; + } - if (filename_len < sizeof(filename_buffer)) { - strncpy(filename_buffer, filename_start, filename_len); - filename_buffer[filename_len] = '\0'; - filename = filename_buffer; + // If auth rules are completely disabled, skip all validation and allow upload + if (!auth_required) { + fprintf(stderr, "AUTH: Authentication rules disabled - skipping all " + "validation and allowing upload\n"); + // Skip validation and proceed to file processing + goto process_file_upload; + } - } else { + // Authentication is handled by centralized validation system + // TODO: Get uploader_pubkey from centralized validation result + // For now, keep existing uploader_pubkey extraction for compatibility - } - } else { +process_file_upload: + // Get dimensions from in-memory buffer before saving file + int width = 0, height = 0; + nip94_get_dimensions(file_data, content_length, content_type, &width, + &height); - } - } else { + // Determine file extension from Content-Type using centralized mapping + const char *extension = mime_to_extension(content_type); - // Unquoted filename - extract until space or end - const char* filename_end = filename_start; - while (*filename_end && *filename_end != ' ' && *filename_end != ';') { - filename_end++; - } - static char filename_buffer[256]; - size_t filename_len = filename_end - filename_start; + // Save file to blobs directory with SHA-256 + extension + char filepath[MAX_PATH_LEN]; + snprintf(filepath, sizeof(filepath), "blobs/%s%s", sha256_hex, extension); - if (filename_len < sizeof(filename_buffer)) { - strncpy(filename_buffer, filename_start, filename_len); - filename_buffer[filename_len] = '\0'; - filename = filename_buffer; + FILE *outfile = fopen(filepath, "wb"); + if (!outfile) { + free(file_data); + printf("Status: 500 Internal Server Error\r\n"); + printf("Content-Type: text/plain\r\n\r\n"); + printf("Failed to create file\n"); + return; + } - } else { + size_t bytes_written = fwrite(file_data, 1, content_length, outfile); + fclose(outfile); - } - } + // Set file permissions to 644 (owner read/write, group/others read) - + // standard for web files + if (chmod(filepath, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) != 0) { + fprintf(stderr, "WARNING: Failed to set file permissions for %s\r\n", + filepath); + // Continue anyway - this is not a fatal error + } else { + } + free(file_data); + + if (bytes_written != (size_t)content_length) { + // Clean up partial file + unlink(filepath); + printf("Status: 500 Internal Server Error\r\n"); + printf("Content-Type: text/plain\r\n\r\n"); + printf("Failed to write complete file\n"); + return; + } + + // Extract filename from Content-Disposition header if present + const char *filename = NULL; + const char *content_disposition = getenv("HTTP_CONTENT_DISPOSITION"); + + if (content_disposition) { + + // Look for filename= in Content-Disposition header + const char *filename_start = strstr(content_disposition, "filename="); + if (filename_start) { + + filename_start += 9; // Skip "filename=" + + // Handle quoted filenames + if (*filename_start == '"') { + + filename_start++; // Skip opening quote + // Find closing quote + const char *filename_end = strchr(filename_start, '"'); + if (filename_end) { + // Extract filename between quotes + static char filename_buffer[256]; + size_t filename_len = filename_end - filename_start; + + if (filename_len < sizeof(filename_buffer)) { + strncpy(filename_buffer, filename_start, filename_len); + filename_buffer[filename_len] = '\0'; + filename = filename_buffer; + + } else { + } } else { - } + } else { + + // Unquoted filename - extract until space or end + const char *filename_end = filename_start; + while (*filename_end && *filename_end != ' ' && *filename_end != ';') { + filename_end++; + } + static char filename_buffer[256]; + size_t filename_len = filename_end - filename_start; + + if (filename_len < sizeof(filename_buffer)) { + strncpy(filename_buffer, filename_start, filename_len); + filename_buffer[filename_len] = '\0'; + filename = filename_buffer; + + } else { + } + } } else { - } - + } else { + } - - // Store blob metadata in database - time_t uploaded_time = time(NULL); - if (!insert_blob_metadata(sha256_hex, content_length, content_type, uploaded_time, uploader_pubkey, filename)) { - // Database insertion failed - clean up the physical file to maintain consistency + // Store blob metadata in database + time_t uploaded_time = time(NULL); + if (!insert_blob_metadata(sha256_hex, content_length, content_type, + uploaded_time, uploader_pubkey, filename)) { + // Database insertion failed - clean up the physical file to maintain + // consistency - unlink(filepath); - printf("Status: 500 Internal Server Error\r\n"); - printf("Content-Type: text/plain\r\n\r\n"); - printf("Failed to store blob metadata\n"); - return; - } - + unlink(filepath); + printf("Status: 500 Internal Server Error\r\n"); + printf("Content-Type: text/plain\r\n\r\n"); + printf("Failed to store blob metadata\n"); + return; + } - - // Get origin from config - char origin[256]; - nip94_get_origin(origin, sizeof(origin)); - - // Build canonical blob URL - char blob_url[512]; - nip94_build_blob_url(origin, sha256_hex, content_type, blob_url, sizeof(blob_url)); - - // Return success response with blob descriptor - printf("Status: 200 OK\r\n"); - printf("Content-Type: application/json\r\n\r\n"); - printf("{\n"); - printf(" \"sha256\": \"%s\",\n", sha256_hex); - printf(" \"size\": %ld,\n", content_length); - printf(" \"type\": \"%s\",\n", content_type); - printf(" \"uploaded\": %ld,\n", uploaded_time); - printf(" \"url\": \"%s\"", blob_url); - - // Add NIP-94 metadata if enabled - if (nip94_is_enabled()) { - printf(",\n"); - nip94_emit_field(blob_url, content_type, sha256_hex, content_length, width, height); - } - - printf("\n}\n"); - + // Get origin from config + char origin[256]; + nip94_get_origin(origin, sizeof(origin)); + // Build canonical blob URL + char blob_url[512]; + nip94_build_blob_url(origin, sha256_hex, content_type, blob_url, + sizeof(blob_url)); + + // Return success response with blob descriptor + printf("Status: 200 OK\r\n"); + printf("Content-Type: application/json\r\n\r\n"); + printf("{\n"); + printf(" \"sha256\": \"%s\",\n", sha256_hex); + printf(" \"size\": %ld,\n", content_length); + printf(" \"type\": \"%s\",\n", content_type); + printf(" \"uploaded\": %ld,\n", uploaded_time); + printf(" \"url\": \"%s\"", blob_url); + + // Add NIP-94 metadata if enabled + if (nip94_is_enabled()) { + printf(",\n"); + nip94_emit_field(blob_url, content_type, sha256_hex, content_length, width, + height); + } + + printf("\n}\n"); } ///////////////////////////////////////////////////////////////////////////////////////// @@ -1434,60 +1165,47 @@ void handle_upload_request(void) { ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// -// Get NIP-42 mode from configuration -int get_nip42_mode_config(void) { - if (!g_server_config.config_loaded) { - return NIP42_MODE_OPTIONAL; // Default mode - } - - if (strcmp(g_server_config.require_nip42_auth, "disabled") == 0) { - return NIP42_MODE_DISABLED; - } else if (strcmp(g_server_config.require_nip42_auth, "required") == 0) { - return NIP42_MODE_REQUIRED; - } else { - return NIP42_MODE_OPTIONAL; // Default for "optional" or unknown values - } -} - // Handle GET /auth requests to provide NIP-42 challenges void handle_auth_challenge_request(void) { - // Log the incoming request - log_request("GET", "/auth", "challenge_request", 0); - - // Check if NIP-42 is disabled - int nip42_mode = get_nip42_mode_config(); - if (nip42_mode == NIP42_MODE_DISABLED) { - send_error_response(404, "not_found", "NIP-42 authentication is disabled", - "This server does not support NIP-42 authentication"); - log_request("GET", "/auth", "disabled", 404); - return; - } - - // Generate a new challenge using the request validator - nostr_nip42_challenge_t challenge; - const char* client_ip = getenv("REMOTE_ADDR"); - int result = nostr_request_validator_generate_nip42_challenge(&challenge, client_ip); - if (result != NOSTR_SUCCESS) { - send_error_response(500, "challenge_generation_failed", "Failed to generate challenge", - "Internal server error during challenge generation"); - log_request("GET", "/auth", "generation_failed", 500); - return; - } - - // Return the challenge as JSON - printf("Status: 200 OK\r\n"); - printf("Content-Type: application/json\r\n"); - printf("Access-Control-Allow-Origin: *\r\n"); - printf("Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n"); - printf("Access-Control-Allow-Headers: Content-Type, Authorization\r\n"); - printf("\r\n"); + // Log the incoming request + log_request("GET", "/auth", "challenge_request", 0); + + // Use the validator's challenge generation system + char challenge_buffer[65]; + const char *client_ip = getenv("REMOTE_ADDR"); + + // Generate and store challenge using validator system + int result = nostr_generate_nip42_challenge(challenge_buffer, sizeof(challenge_buffer), client_ip); + + if (result != NOSTR_SUCCESS) { + printf("Status: 500 Internal Server Error\r\n"); + printf("Content-Type: application/json\r\n\r\n"); printf("{\n"); - printf(" \"challenge\": \"%s\",\n", challenge.challenge_id); - printf(" \"relay\": \"ginxsom\",\n"); - printf(" \"expires\": %ld\n", challenge.expires_at); + printf(" \"error\": \"challenge_generation_failed\",\n"); + printf(" \"message\": \"Failed to generate authentication challenge\"\n"); printf("}\n"); - - log_request("GET", "/auth", "challenge_generated", 200); + log_request("GET", "/auth", "challenge_failed", 500); + return; + } + + // Calculate expiration (current time + challenge timeout) + // Default timeout is 600 seconds (10 minutes) as per NIP-42 challenge manager + time_t expires_at = time(NULL) + 600; + + // Return the challenge as JSON + printf("Status: 200 OK\r\n"); + printf("Content-Type: application/json\r\n"); + printf("Access-Control-Allow-Origin: *\r\n"); + printf("Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n"); + printf("Access-Control-Allow-Headers: Content-Type, Authorization\r\n"); + printf("\r\n"); + printf("{\n"); + printf(" \"challenge\": \"%s\",\n", challenge_buffer); + printf(" \"relay\": \"ginxsom\",\n"); + printf(" \"expires\": %ld\n", expires_at); + printf("}\n"); + + log_request("GET", "/auth", "challenge_generated", 200); } ///////////////////////////////////////////////////////////////////////////////////////// @@ -1496,138 +1214,272 @@ void handle_auth_challenge_request(void) { ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// - int main(void) { - // Initialize server configuration and identity - // Try file-based config first, then fall back to database config - char config_path[512]; - int config_loaded = 0; - - if (get_config_file_path(config_path, sizeof(config_path))) { - fprintf(stderr, "STARTUP: Checking for config file at: %s\n", config_path); - if (load_server_config(config_path)) { - fprintf(stderr, "STARTUP: File-based configuration loaded successfully\n"); - config_loaded = 1; - } else { - fprintf(stderr, "STARTUP: No valid file-based config found, trying database config\n"); - } - } - - // Fall back to database configuration if file config failed - if (!config_loaded && !initialize_server_config()) { - fprintf(stderr, "STARTUP: No configuration found - server starting in setup mode\n"); - fprintf(stderr, "STARTUP: Run interactive setup with: ginxsom --setup\n"); - // For interactive mode (when stdin is available), offer setup - if (isatty(STDIN_FILENO) && get_config_file_path(config_path, sizeof(config_path))) { - return run_interactive_setup(config_path); - } - } else if (!config_loaded) { - fprintf(stderr, "STARTUP: Database configuration loaded successfully\n"); - } - - // CRITICAL: Initialize nostr crypto system for cryptographic operations - fprintf(stderr, "STARTUP: Initializing nostr crypto system...\r\n"); - if (nostr_crypto_init() != 0) { - fprintf(stderr, "FATAL ERROR: Failed to initialize nostr crypto system\r\n"); - return 1; - } - fprintf(stderr, "STARTUP: nostr crypto system initialized successfully\r\n"); - - // Initialize request validator system - fprintf(stderr, "STARTUP: Initializing request validator system...\r\n"); - int validator_init_result = nostr_request_validator_init(DB_PATH, "ginxsom"); - fprintf(stderr, "MAIN: validator init return code: %d\r\n", validator_init_result); - if (validator_init_result != NOSTR_SUCCESS) { - fprintf(stderr, "FATAL ERROR: Failed to initialize request validator system\r\n"); - return 1; - } - fprintf(stderr, "STARTUP: Request validator system initialized successfully\r\n"); - fflush(stderr); - while (FCGI_Accept() >= 0) { - const char* request_method = getenv("REQUEST_METHOD"); - const char* request_uri = getenv("REQUEST_URI"); - - - if (!request_method || !request_uri) { - printf("Status: 400 Bad Request\r\n"); - printf("Content-Type: text/plain\r\n\r\n"); - printf("Invalid request\n"); - continue; - } - - // Route HEAD /upload pre-flight (BUD-06) before generic HEAD blob handler - if (strcmp(request_method, "HEAD") == 0 && strcmp(request_uri, "/upload") == 0) { - // Handle HEAD /upload requests (BUD-06 pre-flight validation) - handle_head_upload_request(); - } else if (strcmp(request_method, "HEAD") == 0) { - // Handle HEAD requests for blob metadata - const char* sha256 = extract_sha256_from_uri(request_uri); - if (sha256) { - handle_head_request(sha256); - log_request("HEAD", request_uri, "none", 200); // Assuming success - could be enhanced to track actual status - } else { - printf("Status: 400 Bad Request\r\n"); - printf("Content-Type: text/plain\r\n\r\n"); - printf("Invalid SHA-256 hash in URI\n"); - log_request("HEAD", request_uri, "none", 400); - } - } else if (strcmp(request_method, "PUT") == 0 && strcmp(request_uri, "/upload") == 0) { - // Handle PUT /upload requests with authentication - handle_upload_request(); - } else if (strcmp(request_method, "PUT") == 0 && strcmp(request_uri, "/mirror") == 0) { - // Handle PUT /mirror requests (BUD-04) - handle_mirror_request(); - } else if (strcmp(request_method, "PUT") == 0 && strcmp(request_uri, "/report") == 0) { - // Handle PUT /report requests (BUD-09) - handle_report_request(); - } else if (strncmp(request_uri, "/api/", 5) == 0) { - // Handle admin API requests - handle_admin_api_request(request_method, request_uri); - } else if (strcmp(request_method, "GET") == 0 && strncmp(request_uri, "/list/", 6) == 0) { - // Handle GET /list/ requests - const char* pubkey = request_uri + 6; // Skip "/list/" - - // Extract pubkey from URI (remove query string if present) - static char pubkey_buffer[65]; - const char* query_start = strchr(pubkey, '?'); - size_t pubkey_len; - - if (query_start) { - pubkey_len = query_start - pubkey; - } else { - pubkey_len = strlen(pubkey); - } - - if (pubkey_len == 64) { // Valid pubkey length - strncpy(pubkey_buffer, pubkey, 64); - pubkey_buffer[64] = '\0'; - handle_list_request(pubkey_buffer); - } else { - send_error_response(400, "invalid_pubkey", "Invalid pubkey format", "Pubkey must be 64 hex characters"); - log_request("GET", request_uri, "none", 400); - } - } else if (strcmp(request_method, "GET") == 0 && strcmp(request_uri, "/auth") == 0) { - // Handle GET /auth requests for NIP-42 challenge generation - handle_auth_challenge_request(); - } else if (strcmp(request_method, "DELETE") == 0) { - // Handle DELETE / requests - const char* sha256 = extract_sha256_from_uri(request_uri); + // Initialize server configuration and identity + // Try file-based config first, then fall back to database config + char config_path[512]; + int config_loaded = 0; - if (sha256) { - handle_delete_request(sha256); - } else { - send_error_response(400, "invalid_hash", "Invalid SHA-256 hash in URI", "URI must contain a valid 64-character hex hash"); - log_request("DELETE", request_uri, "none", 400); - } - } else { - // Other methods not implemented yet - printf("Status: 501 Not Implemented\r\n"); - printf("Content-Type: text/plain\r\n\r\n"); - printf("Method %s not implemented\n", request_method); - log_request(request_method, request_uri, "none", 501); - } + if (get_config_file_path(config_path, sizeof(config_path))) { + fprintf(stderr, "STARTUP: Checking for config file at: %s\n", config_path); + if (load_server_config(config_path)) { + fprintf(stderr, + "STARTUP: File-based configuration loaded successfully\n"); + config_loaded = 1; + } else { + fprintf(stderr, "STARTUP: No valid file-based config found, trying " + "database config\n"); + } + } + + // Fall back to database configuration if file config failed + if (!config_loaded && !initialize_server_config()) { + fprintf( + stderr, + "STARTUP: No configuration found - server starting in setup mode\n"); + fprintf(stderr, "STARTUP: Run interactive setup with: ginxsom --setup\n"); + // For interactive mode (when stdin is available), offer setup + if (isatty(STDIN_FILENO) && + get_config_file_path(config_path, sizeof(config_path))) { + return run_interactive_setup(config_path); + } + } else if (!config_loaded) { + fprintf(stderr, "STARTUP: Database configuration loaded successfully\n"); + } + + // CRITICAL: Initialize nostr crypto system for cryptographic operations + fprintf(stderr, "STARTUP: Initializing nostr crypto system...\r\n"); + if (nostr_crypto_init() != 0) { + fprintf(stderr, + "FATAL ERROR: Failed to initialize nostr crypto system\r\n"); + return 1; + } + fprintf(stderr, "STARTUP: nostr crypto system initialized successfully\r\n"); + + // Initialize request validator system + fprintf(stderr, "STARTUP: Initializing request validator system...\r\n"); + int validator_init_result = + ginxsom_request_validator_init(DB_PATH, "ginxsom"); + fprintf(stderr, "MAIN: validator init return code: %d\r\n", + validator_init_result); + if (validator_init_result != NOSTR_SUCCESS) { + fprintf(stderr, + "FATAL ERROR: Failed to initialize request validator system\r\n"); + return 1; + } + fprintf(stderr, + "STARTUP: Request validator system initialized successfully\r\n"); + fflush(stderr); + ///////////////////////////////////////////////////////////////////// + // THIS IS WHERE THE REQUESTS ENTER THE FastCGI + ///////////////////////////////////////////////////////////////////// + while (FCGI_Accept() >= 0) { + const char *request_method = getenv("REQUEST_METHOD"); + const char *request_uri = getenv("REQUEST_URI"); + const char *auth_header = getenv("HTTP_AUTHORIZATION"); + + if (!request_method || !request_uri) { + printf("Status: 400 Bad Request\r\n"); + printf("Content-Type: text/plain\r\n\r\n"); + printf("Invalid request\n"); + continue; + } + + ///////////////////////////////////////////////////////////////////// + // CENTRALIZED REQUEST VALIDATION SYSTEM + ///////////////////////////////////////////////////////////////////// + + // Determine operation from request method and URI + const char *operation = "unknown"; + const char *resource_hash = NULL; + + if (strcmp(request_method, "HEAD") == 0 && strcmp(request_uri, "/upload") == 0) { + operation = "head_upload"; + } else if (strcmp(request_method, "HEAD") == 0) { + operation = "head"; + resource_hash = extract_sha256_from_uri(request_uri); + } else if (strcmp(request_method, "PUT") == 0 && strcmp(request_uri, "/upload") == 0) { + operation = "upload"; + } else if (strcmp(request_method, "PUT") == 0 && strcmp(request_uri, "/mirror") == 0) { + operation = "mirror"; + } else if (strcmp(request_method, "PUT") == 0 && strcmp(request_uri, "/report") == 0) { + operation = "report"; + } else if (strncmp(request_uri, "/api/", 5) == 0) { + operation = "admin"; + } else if (strcmp(request_method, "GET") == 0 && strncmp(request_uri, "/list/", 6) == 0) { + operation = "list"; + } else if (strcmp(request_method, "GET") == 0 && strcmp(request_uri, "/auth") == 0) { + operation = "challenge"; + } else if (strcmp(request_method, "DELETE") == 0) { + operation = "delete"; + resource_hash = extract_sha256_from_uri(request_uri); } - return 0; + // Declare result structure + nostr_request_result_t result; + + // Create unified request structure + nostr_unified_request_t request = { + .operation = operation, + .auth_header = auth_header, + .event = NULL, + .resource_hash = resource_hash, + .mime_type = getenv("CONTENT_TYPE"), + .file_size = getenv("CONTENT_LENGTH") ? atol(getenv("CONTENT_LENGTH")) : 0, + .request_url = "ginxsom", + .challenge_id = NULL, // Validator will extract from NIP-42 event content + .nip42_enabled = 1, // Let validator check actual config + .client_ip = getenv("REMOTE_ADDR"), + .app_context = NULL + }; + + // Validate the request through unified system + int validation_result = nostr_validate_unified_request(&request, &result); + + // Handle validation failures (except for certain cases) + if (validation_result != NOSTR_SUCCESS || !result.valid) { + // Special case: challenge generation failure should be handled by the endpoint + if (strcmp(operation, "challenge") == 0) { + // Let the /auth endpoint handle this - it will generate its own error response + } else if (strcmp(operation, "head") == 0 || strcmp(operation, "head_upload") == 0) { + // HEAD requests might not require auth depending on config - let handler decide + } else if (strcmp(operation, "list") == 0) { + // List operation might be optional auth - let handler decide + } else { + // For other operations, validation failure means auth failure + const char *message = result.reason[0] ? result.reason : "Authentication failed"; + const char *details = "Authentication validation failed"; + + // Always include event JSON in details when auth header is provided for debugging + if (auth_header) { + // Parse event JSON from auth header to show in details for debugging + static char event_json[8192]; // Increased buffer size + if (strncasecmp(auth_header, "nostr ", 6) == 0) { + // Decode base64 to get event JSON + const char *base64_event = auth_header + 6; + unsigned char decoded_buffer[8192]; // Increased buffer size + size_t decoded_len = base64_decode(base64_event, decoded_buffer); + if (decoded_len > 0 && decoded_len < sizeof(event_json)) { + memcpy(event_json, decoded_buffer, decoded_len); + event_json[decoded_len] = '\0'; + details = event_json; // Use the event JSON as details for debugging + } + } + } + + send_error_response(401, "authentication_failed", message, details); + log_request(request_method, request_uri, "auth_failed", 401); + continue; + } + } + + + ///////////////////////////////////////////////////////////////////// + // ROUTE THE NOW VALID REQUEST TO HANDLERS + ///////////////////////////////////////////////////////////////////// + + // Route HEAD /upload pre-flight (BUD-06) before generic HEAD blob handler + if (strcmp(request_method, "HEAD") == 0 && + strcmp(request_uri, "/upload") == 0) { + // Handle HEAD /upload requests (BUD-06 pre-flight validation) + handle_head_upload_request(); + + + + + } else if (strcmp(request_method, "HEAD") == 0) { + // Handle HEAD requests for blob metadata + const char *sha256 = extract_sha256_from_uri(request_uri); + if (sha256) { + handle_head_request(sha256); + log_request("HEAD", request_uri, "public", 200); + } else { + printf("Status: 400 Bad Request\r\n"); + printf("Content-Type: text/plain\r\n\r\n"); + printf("Invalid SHA-256 hash in URI\n"); + log_request("HEAD", request_uri, "none", 400); + } + + + + } else if (strcmp(request_method, "PUT") == 0 && + strcmp(request_uri, "/upload") == 0) { + // Handle PUT /upload requests with pre-validated auth + // TODO: Pass validated result to existing handler + handle_upload_request(); + + + + } else if (strcmp(request_method, "PUT") == 0 && + strcmp(request_uri, "/mirror") == 0) { + // Handle PUT /mirror requests (BUD-04) + handle_mirror_request(); + + + + } else if (strcmp(request_method, "PUT") == 0 && + strcmp(request_uri, "/report") == 0) { + // Handle PUT /report requests (BUD-09) + handle_report_request(); + + + + } else if (strncmp(request_uri, "/api/", 5) == 0) { + // Handle admin API requests with pre-validated auth + // TODO: Pass validated result to existing handler + handle_admin_api_request(request_method, request_uri); + + + } else if (strcmp(request_method, "GET") == 0 && + strncmp(request_uri, "/list/", 6) == 0) { + // Handle GET /list/ requests with pre-validated auth + const char *pubkey = request_uri + 6; // Skip "/list/" + + // Extract pubkey from URI (remove query string if present) + static char pubkey_buffer[65]; + const char *query_start = strchr(pubkey, '?'); + size_t pubkey_len; + + if (query_start) { + pubkey_len = query_start - pubkey; + } else { + pubkey_len = strlen(pubkey); + } + + if (pubkey_len == 64) { // Valid pubkey length + strncpy(pubkey_buffer, pubkey, 64); + pubkey_buffer[64] = '\0'; + // TODO: Pass validated result to existing handler + handle_list_request(pubkey_buffer); + } else { + send_error_response(400, "invalid_pubkey", "Invalid pubkey format", + "Pubkey must be 64 hex characters"); + log_request("GET", request_uri, "none", 400); + } + } else if (strcmp(request_method, "GET") == 0 && + strcmp(request_uri, "/auth") == 0) { + // Handle GET /auth requests using the existing handler + handle_auth_challenge_request(); + } else if (strcmp(request_method, "DELETE") == 0) { + // Handle DELETE / requests with pre-validated auth + const char *sha256 = extract_sha256_from_uri(request_uri); + if (sha256) { + // TODO: Pass validated result to existing handler + handle_delete_request(sha256); + } else { + send_error_response(400, "invalid_hash", "Invalid SHA-256 hash in URI", + "URI must contain a valid 64-character hex hash"); + log_request("DELETE", request_uri, "none", 400); + } + } else { + // Other methods not implemented yet + printf("Status: 501 Not Implemented\r\n"); + printf("Content-Type: text/plain\r\n\r\n"); + printf("Method %s not implemented\n", request_method); + log_request(request_method, request_uri, "none", 501); + } + } + + return 0; } diff --git a/src/request_validator.c b/src/request_validator.c index 5605d16..f01983f 100644 --- a/src/request_validator.c +++ b/src/request_validator.c @@ -2,48 +2,76 @@ * Ginxsom Request Validator - Integrated Authentication System * * Provides complete request validation including: - * - Protocol validation via nostr_core_lib (signatures, pubkey extraction, NIP-42) + * - Protocol validation via nostr_core_lib (signatures, pubkey extraction, + * NIP-42) * - Database-driven authorization rules (whitelist, blacklist, size limits) * - Memory caching for performance * - SQLite integration for ginxsom-specific needs */ #define _GNU_SOURCE +#include "../nostr_core_lib/cjson/cJSON.h" +#include "../nostr_core_lib/nostr_core/nip001.h" +#include "../nostr_core_lib/nostr_core/nip042.h" +#include "../nostr_core_lib/nostr_core/nostr_common.h" +#include "../nostr_core_lib/nostr_core/utils.h" +#include "ginxsom.h" +#include #include #include #include #include #include -#include -#include "../nostr_core_lib/nostr_core/nostr_common.h" -#include "../nostr_core_lib/nostr_core/nip001.h" -#include "../nostr_core_lib/nostr_core/nip042.h" -#include "../nostr_core_lib/nostr_core/utils.h" -#include "../nostr_core_lib/cjson/cJSON.h" -#include "ginxsom.h" // Additional error codes for ginxsom-specific functionality #define NOSTR_ERROR_CRYPTO_INIT -100 #define NOSTR_ERROR_AUTH_REQUIRED -101 #define NOSTR_ERROR_NIP42_DISABLED -102 #define NOSTR_ERROR_EVENT_EXPIRED -103 +// Note: NOSTR_ERROR_NIP42_CHALLENGE_NOT_FOUND and +// NOSTR_ERROR_NIP42_CHALLENGE_EXPIRED are already defined in +// nostr_core_lib/nostr_core/nostr_common.h // Database path (consistent with main.c) #define DB_PATH "db/ginxsom.db" +// NIP-42 challenge management constants +#define MAX_CHALLENGES 1000 +#define CHALLENGE_CLEANUP_INTERVAL 300 // 5 minutes + //============================================================================= // DATA STRUCTURES //============================================================================= +// NIP-42 challenge storage +typedef struct { + char challenge_id[65]; + char client_ip[64]; + time_t created_at; + time_t expires_at; + int active; +} nip42_challenge_entry_t; + +// NIP-42 challenge management +typedef struct { + nip42_challenge_entry_t challenges[MAX_CHALLENGES]; + int challenge_count; + time_t last_cleanup; + int timeout_seconds; + int time_tolerance_seconds; +} nip42_challenge_manager_t; + // Cached configuration structure typedef struct { - int auth_required; // Whether authentication is required - long max_file_size; // Maximum file size in bytes - int admin_enabled; // Whether admin interface is enabled - char admin_pubkey[65]; // Admin public key - int nip42_mode; // NIP-42 authentication mode - time_t cache_expires; // When cache expires - int cache_valid; // Whether cache is valid + int auth_required; // Whether authentication is required + long max_file_size; // Maximum file size in bytes + int admin_enabled; // Whether admin interface is enabled + char admin_pubkey[65]; // Admin public key + int nip42_mode; // NIP-42 authentication mode + int nip42_challenge_timeout; // NIP-42 challenge timeout in seconds + int nip42_time_tolerance; // NIP-42 time tolerance in seconds + time_t cache_expires; // When cache expires + int cache_valid; // Whether cache is valid } auth_config_cache_t; //============================================================================= @@ -51,23 +79,25 @@ typedef struct { //============================================================================= static auth_config_cache_t g_auth_cache = {0}; +static nip42_challenge_manager_t g_challenge_manager = {0}; static int g_validator_initialized = 0; // Last rule violation details for status code mapping struct { - char violation_type[100]; // "pubkey_blacklist", "hash_blacklist", "whitelist_violation", etc. - char reason[500]; // specific reason string + char violation_type[100]; // "pubkey_blacklist", "hash_blacklist", + // "whitelist_violation", etc. + char reason[500]; // specific reason string } g_last_rule_violation = {0}; /** * Helper function for consistent debug logging to our debug.log file */ -static void validator_debug_log(const char* message) { - FILE* debug_log = fopen("logs/app/debug.log", "a"); - if (debug_log) { - fprintf(debug_log, "%ld %s", (long)time(NULL), message); - fclose(debug_log); - } +static void validator_debug_log(const char *message) { + FILE *debug_log = fopen("logs/app/debug.log", "a"); + if (debug_log) { + fprintf(debug_log, "%ld %s", (long)time(NULL), message); + fclose(debug_log); + } } //============================================================================= @@ -75,13 +105,24 @@ static void validator_debug_log(const char* message) { //============================================================================= static int reload_auth_config(void); -static int parse_authorization_header(const char* auth_header, char* event_json, size_t json_size); -static int extract_pubkey_from_event(cJSON* event, char* pubkey_buffer, size_t buffer_size); -static int validate_blossom_event(cJSON* event, const char* expected_hash, const char* method); -static int validate_nip42_event(cJSON* event, const char* relay_url, const char* challenge_id); -static int check_database_auth_rules(const char* pubkey, const char* operation, const char* resource_hash); +static int parse_authorization_header(const char *auth_header, char *event_json, + size_t json_size); +static int extract_pubkey_from_event(cJSON *event, char *pubkey_buffer, + size_t buffer_size); +static int validate_blossom_event(cJSON *event, const char *expected_hash, + const char *method); +static int validate_nip42_event(cJSON *event, const char *relay_url, + const char *challenge_id); +static int check_database_auth_rules(const char *pubkey, const char *operation, + const char *resource_hash); void nostr_request_validator_clear_violation(void); +// NIP-42 challenge management functions +static void cleanup_expired_challenges(void); +static int store_challenge(const char *challenge_id, const char *client_ip); +static int validate_challenge(const char *challenge_id); +static int generate_challenge_id(char *challenge_buffer, size_t buffer_size); + //============================================================================= // MAIN API FUNCTIONS //============================================================================= @@ -89,335 +130,656 @@ void nostr_request_validator_clear_violation(void); /** * Initialize the ginxsom request validator system */ -int nostr_request_validator_init(const char* db_path, const char* app_name) { - // Mark db_path as unused to suppress warning - it's for future use - (void)db_path; - (void)app_name; +int ginxsom_request_validator_init(const char *db_path, const char *app_name) { + // Mark db_path as unused to suppress warning - it's for future use + (void)db_path; + (void)app_name; - if (g_validator_initialized) { - return NOSTR_SUCCESS; // Already initialized - } + if (g_validator_initialized) { + return NOSTR_SUCCESS; // Already initialized + } - // Initialize nostr_core_lib if not already done - if (nostr_crypto_init() != NOSTR_SUCCESS) { - validator_debug_log("VALIDATOR: Failed to initialize nostr crypto system\n"); - return NOSTR_ERROR_CRYPTO_INIT; - } + // Initialize nostr_core_lib if not already done + if (nostr_crypto_init() != NOSTR_SUCCESS) { + validator_debug_log( + "VALIDATOR: Failed to initialize nostr crypto system\n"); + return NOSTR_ERROR_CRYPTO_INIT; + } - // Load initial configuration from database - int result = reload_auth_config(); - if (result != NOSTR_SUCCESS) { - validator_debug_log("VALIDATOR: Failed to load configuration from database\n"); - return result; - } + // Load initial configuration from database + int result = reload_auth_config(); + if (result != NOSTR_SUCCESS) { + validator_debug_log( + "VALIDATOR: Failed to load configuration from database\n"); + return result; + } - g_validator_initialized = 1; - validator_debug_log("VALIDATOR: Request validator initialized successfully\n"); - return NOSTR_SUCCESS; + // Initialize NIP-42 challenge manager + memset(&g_challenge_manager, 0, sizeof(g_challenge_manager)); + g_challenge_manager.timeout_seconds = + g_auth_cache.nip42_challenge_timeout > 0 + ? g_auth_cache.nip42_challenge_timeout + : 600; + g_challenge_manager.time_tolerance_seconds = + g_auth_cache.nip42_time_tolerance > 0 ? g_auth_cache.nip42_time_tolerance + : 300; + g_challenge_manager.last_cleanup = time(NULL); + + g_validator_initialized = 1; + validator_debug_log( + "VALIDATOR: Request validator initialized successfully\n"); + return NOSTR_SUCCESS; } /** * Check if authentication rules are enabled */ int nostr_auth_rules_enabled(void) { - // Reload config if cache expired - if (!g_auth_cache.cache_valid || time(NULL) > g_auth_cache.cache_expires) { - reload_auth_config(); - } - - return g_auth_cache.auth_required; + // Reload config if cache expired + if (!g_auth_cache.cache_valid || time(NULL) > g_auth_cache.cache_expires) { + reload_auth_config(); + } + + return g_auth_cache.auth_required; } -/** - * Main request validation function - this is the primary entry point - */ -int nostr_validate_request(const nostr_request_t* request, nostr_request_result_t* result) { - // Clear previous violation details - nostr_request_validator_clear_violation(); +/////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////// +// MAIN VALIDATION OF REQUEST +/////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////// +int nostr_validate_unified_request(const nostr_unified_request_t *request, + nostr_request_result_t *result) { + // Clear previous violation details + nostr_request_validator_clear_violation(); - // Simple test debug log - validator_debug_log("VALIDATOR_DEBUG: nostr_validate_request() was called\n"); - validator_debug_log("VALIDATOR_DEBUG: Starting request validation\n"); - if (!g_validator_initialized) { - validator_debug_log("VALIDATOR_DEBUG: STEP 1 FAILED - System not initialized\n"); - return NOSTR_ERROR_INVALID_INPUT; - } - validator_debug_log("VALIDATOR_DEBUG: STEP 1 PASSED - System initialized\n"); + ///////////////////////////////////////////////////////////////////// + // PHASE 1: INPUT VALIDATION (Immediate Rejection ~1μs) + ///////////////////////////////////////////////////////////////////// - if (!request || !result) { - validator_debug_log("VALIDATOR_DEBUG: STEP 2 FAILED - Invalid input parameters\n"); - return NOSTR_ERROR_INVALID_INPUT; - } - validator_debug_log("VALIDATOR_DEBUG: STEP 2 PASSED - Input parameters valid\n"); - - // Initialize result structure - memset(result, 0, sizeof(nostr_request_result_t)); - result->valid = 1; // Default allow - result->error_code = NOSTR_SUCCESS; - strcpy(result->reason, "No validation required"); - - // Reload config if needed - if (!g_auth_cache.cache_valid || time(NULL) > g_auth_cache.cache_expires) { - validator_debug_log("VALIDATOR_DEBUG: Reloading configuration cache\n"); - reload_auth_config(); - } - char config_msg[256]; - sprintf(config_msg, "VALIDATOR_DEBUG: STEP 3 PASSED - Configuration loaded (auth_required=%d)\n", g_auth_cache.auth_required); - validator_debug_log(config_msg); + // 1. Null Pointer Checks - Reject malformed requests instantly + if (!request || !result) { + return NOSTR_ERROR_INVALID_INPUT; + } - // Check if authentication is disabled first (regardless of header presence) - if (!g_auth_cache.auth_required) { - validator_debug_log("VALIDATOR_DEBUG: STEP 4 PASSED - Authentication disabled, allowing request\n"); - strcpy(result->reason, "Authentication disabled"); - return NOSTR_SUCCESS; - } - - // If no auth header provided but auth is required, fail - if (!request->auth_header) { - validator_debug_log("VALIDATOR_DEBUG: STEP 4 FAILED - Auth required but no header provided\n"); - result->valid = 0; - result->error_code = NOSTR_ERROR_AUTH_REQUIRED; - strcpy(result->reason, "Authentication required but not provided"); - return NOSTR_SUCCESS; - } - char header_msg[110]; - sprintf(header_msg, "VALIDATOR_DEBUG: STEP 4 PASSED - Auth header provided: %.50s...\n", request->auth_header); - validator_debug_log(header_msg); - - // Parse authorization header - char event_json[4096]; - int parse_result = parse_authorization_header(request->auth_header, event_json, sizeof(event_json)); - if (parse_result != NOSTR_SUCCESS) { - char parse_msg[256]; - sprintf(parse_msg, "VALIDATOR_DEBUG: STEP 5 FAILED - Failed to parse authorization header (error=%d)\n", parse_result); - validator_debug_log(parse_msg); - result->valid = 0; - result->error_code = parse_result; - strcpy(result->reason, "Failed to parse authorization header"); - return NOSTR_SUCCESS; - } - char parse_success_msg[512]; - sprintf(parse_success_msg, "VALIDATOR_DEBUG: STEP 5 PASSED - Authorization header parsed, JSON: %.100s...\n", event_json); - validator_debug_log(parse_success_msg); - - // Parse JSON event - cJSON* event = cJSON_Parse(event_json); - if (!event) { - validator_debug_log("VALIDATOR_DEBUG: STEP 6 FAILED - Invalid JSON in authorization\n"); - result->valid = 0; - result->error_code = NOSTR_ERROR_EVENT_INVALID_CONTENT; - strcpy(result->reason, "Invalid JSON in authorization"); - return NOSTR_SUCCESS; - } - validator_debug_log("VALIDATOR_DEBUG: STEP 6 PASSED - JSON parsed successfully\n"); - - // Validate NOSTR event structure and signature using nostr_core_lib - int validation_result = nostr_validate_event(event); - if (validation_result != NOSTR_SUCCESS) { - char validation_msg[256]; - sprintf(validation_msg, "VALIDATOR_DEBUG: STEP 7 FAILED - NOSTR event validation failed (error=%d)\n", validation_result); - validator_debug_log(validation_msg); - result->valid = 0; - result->error_code = validation_result; - strcpy(result->reason, "NOSTR event validation failed"); - cJSON_Delete(event); - return NOSTR_SUCCESS; - } - validator_debug_log("VALIDATOR_DEBUG: STEP 7 PASSED - NOSTR event validation succeeded\n"); - - // Extract pubkey for authorization checks - char extracted_pubkey[65] = {0}; - int extract_result = extract_pubkey_from_event(event, extracted_pubkey, sizeof(extracted_pubkey)); - if (extract_result != NOSTR_SUCCESS) { - char extract_msg[256]; - sprintf(extract_msg, "VALIDATOR_DEBUG: STEP 8 FAILED - Failed to extract pubkey from event (error=%d)\n", extract_result); - validator_debug_log(extract_msg); - result->valid = 0; - result->error_code = extract_result; - strcpy(result->reason, "Failed to extract pubkey from event"); - cJSON_Delete(event); - return NOSTR_SUCCESS; - } - char extract_success_msg[256]; - sprintf(extract_success_msg, "VALIDATOR_DEBUG: STEP 8 PASSED - Extracted pubkey: %s\n", extracted_pubkey); - validator_debug_log(extract_success_msg); - - // Get event kind to determine authentication method - cJSON* kind_json = cJSON_GetObjectItem(event, "kind"); - int event_kind = 0; - if (kind_json && cJSON_IsNumber(kind_json)) { - event_kind = cJSON_GetNumberValue(kind_json); - } - char kind_msg[256]; - sprintf(kind_msg, "VALIDATOR_DEBUG: STEP 9 PASSED - Event kind: %d\n", event_kind); - validator_debug_log(kind_msg); - - // Handle different authentication methods - if (event_kind == NOSTR_NIP42_AUTH_EVENT_KIND) { - char nip42_msg[256]; - sprintf(nip42_msg, "VALIDATOR_DEBUG: STEP 10 - Processing NIP-42 authentication (kind %d)\n", NOSTR_NIP42_AUTH_EVENT_KIND); - validator_debug_log(nip42_msg); + // 2. Initialization Check - Verify system is properly initialized + if (!g_validator_initialized) { + return NOSTR_ERROR_INVALID_INPUT; + } - // NIP-42 authentication (kind 22242) - if (request->nip42_mode == 0) { - validator_debug_log("VALIDATOR_DEBUG: STEP 10 FAILED - NIP-42 authentication is disabled\n"); - result->valid = 0; - result->error_code = NOSTR_ERROR_NIP42_DISABLED; - strcpy(result->reason, "NIP-42 authentication is disabled"); - cJSON_Delete(event); - return NOSTR_SUCCESS; - } + // 3. Basic Structure Validation - Ensure required fields are present + // Initialize result structure + memset(result, 0, sizeof(nostr_request_result_t)); + result->valid = 1; // Default allow + result->error_code = NOSTR_SUCCESS; + strcpy(result->reason, "No validation required"); - if (!request->relay_url || !request->challenge_id) { - validator_debug_log("VALIDATOR_DEBUG: STEP 10 FAILED - NIP-42 requires relay_url and challenge_id\n"); - result->valid = 0; - result->error_code = NOSTR_ERROR_NIP42_NOT_CONFIGURED; - strcpy(result->reason, "NIP-42 authentication requires relay_url and challenge_id"); - cJSON_Delete(event); - return NOSTR_SUCCESS; - } + // Reload config if needed + if (!g_auth_cache.cache_valid || time(NULL) > g_auth_cache.cache_expires) { + reload_auth_config(); + } + char config_msg[256]; + sprintf(config_msg, + "VALIDATOR_DEBUG: STEP 3 PASSED - Configuration loaded " + "(auth_required=%d, nip42_enabled=%d)\n", + g_auth_cache.auth_required, request->nip42_enabled); - int nip42_result = validate_nip42_event(event, request->relay_url, request->challenge_id); - if (nip42_result != NOSTR_SUCCESS) { - char nip42_fail_msg[256]; - sprintf(nip42_fail_msg, "VALIDATOR_DEBUG: STEP 10 FAILED - NIP-42 validation failed (error=%d)\n", nip42_result); - validator_debug_log(nip42_fail_msg); - result->valid = 0; - result->error_code = nip42_result; - strcpy(result->reason, "NIP-42 authentication failed"); - cJSON_Delete(event); - return NOSTR_SUCCESS; - } - validator_debug_log("VALIDATOR_DEBUG: STEP 10 PASSED - NIP-42 authentication succeeded\n"); - strcpy(result->reason, "NIP-42 authentication passed"); - - } else if (event_kind == 24242) { - validator_debug_log("VALIDATOR_DEBUG: STEP 10 - Processing Blossom authentication (kind 24242)\n"); - // Blossom protocol authentication (kind 24242) - if (request->operation && request->resource_hash) { - char blossom_valid_msg[512]; - sprintf(blossom_valid_msg, "VALIDATOR_DEBUG: Validating Blossom event for operation='%s', hash='%s'\n", - request->operation ? request->operation : "NULL", - request->resource_hash ? request->resource_hash : "NULL"); - validator_debug_log(blossom_valid_msg); + // Handle challenge generation operation (no authentication required) + if (request->operation && strcmp(request->operation, "challenge") == 0) { - int blossom_result = validate_blossom_event(event, request->resource_hash, request->operation); - if (blossom_result != NOSTR_SUCCESS) { - char blossom_fail_msg[256]; - sprintf(blossom_fail_msg, "VALIDATOR_DEBUG: STEP 10 FAILED - Blossom validation failed (error=%d)\n", blossom_result); - validator_debug_log(blossom_fail_msg); - result->valid = 0; - result->error_code = blossom_result; - strcpy(result->reason, "Blossom event does not authorize this operation"); - cJSON_Delete(event); - return NOSTR_SUCCESS; - } - } else { - validator_debug_log("VALIDATOR_DEBUG: Skipping Blossom validation (no operation/hash specified)\n"); - } - validator_debug_log("VALIDATOR_DEBUG: STEP 10 PASSED - Blossom authentication succeeded\n"); - strcpy(result->reason, "Blossom authentication passed"); - - } else { - char unsupported_msg[256]; - sprintf(unsupported_msg, "VALIDATOR_DEBUG: STEP 10 FAILED - Unsupported event kind: %d\n", event_kind); - validator_debug_log(unsupported_msg); - result->valid = 0; - result->error_code = NOSTR_ERROR_EVENT_INVALID_KIND; - strcpy(result->reason, "Unsupported event kind for authentication"); - cJSON_Delete(event); - return NOSTR_SUCCESS; + // Check if NIP-42 is enabled + if (!request->nip42_enabled || g_auth_cache.nip42_mode == 0) { + + result->valid = 0; + result->error_code = NOSTR_ERROR_NIP42_DISABLED; + strcpy(result->reason, "NIP-42 authentication is disabled"); + return NOSTR_SUCCESS; } - // Copy validated pubkey to result - if (strlen(extracted_pubkey) == 64) { - strncpy(result->pubkey, extracted_pubkey, 64); - result->pubkey[64] = '\0'; - validator_debug_log("VALIDATOR_DEBUG: STEP 11 PASSED - Pubkey copied to result\n"); - } else { - char pubkey_warning_msg[256]; - sprintf(pubkey_warning_msg, "VALIDATOR_DEBUG: STEP 11 WARNING - Invalid pubkey length: %zu\n", strlen(extracted_pubkey)); - validator_debug_log(pubkey_warning_msg); + // Generate and store challenge + char challenge_id[65]; + int challenge_result = + generate_challenge_id(challenge_id, sizeof(challenge_id)); + if (challenge_result != NOSTR_SUCCESS) { + + result->valid = 0; + result->error_code = challenge_result; + strcpy(result->reason, "Failed to generate challenge ID"); + return NOSTR_SUCCESS; } - cJSON_Delete(event); + // Store challenge in manager + int store_result = store_challenge(challenge_id, request->client_ip); + if (store_result != NOSTR_SUCCESS) { - // STEP 12 PASSED: Protocol validation complete - continue to database rule evaluation - validator_debug_log("VALIDATOR_DEBUG: STEP 12 PASSED - Protocol validation complete, proceeding to rule evaluation\n"); - - validator_debug_log("VALIDATOR_DEBUG: STEP 13 PASSED - Auth rules enabled, checking database rules\n"); - - // Check database rules for authorization - int rules_result = check_database_auth_rules(extracted_pubkey, request->operation, request->resource_hash); - if (rules_result != NOSTR_SUCCESS) { - validator_debug_log("VALIDATOR_DEBUG: STEP 14 FAILED - Database rules denied request\n"); - result->valid = 0; - result->error_code = rules_result; - // Determine specific failure reason based on rules evaluation - if (rules_result == NOSTR_ERROR_AUTH_REQUIRED) { - // This can be pubkey blacklist or whitelist violation - set generic message - // The specific reason will be detailed in the database check function - strcpy(result->reason, "Request denied by authorization rules"); - } else { - strcpy(result->reason, "Authorization error"); - } - return NOSTR_SUCCESS; + result->valid = 0; + result->error_code = store_result; + strcpy(result->reason, "Failed to store challenge"); + return NOSTR_SUCCESS; } - validator_debug_log("VALIDATOR_DEBUG: STEP 14 PASSED - Database rules allow request\n"); - // All validations passed + // Return challenge in result (we'll use the reason field for the challenge + // ID) + snprintf(result->reason, sizeof(result->reason), "CHALLENGE:%s", + challenge_id); result->valid = 1; result->error_code = NOSTR_SUCCESS; - validator_debug_log("VALIDATOR_DEBUG: STEP 15 PASSED - All validations complete, request ALLOWED\n"); + + char challenge_msg[256]; + sprintf(challenge_msg, + "VALIDATOR_DEBUG: STEP 4 PASSED - Challenge generated: %.16s...\n", + challenge_id); + return NOSTR_SUCCESS; + } + + ///////////////////////////////////////////////////////////////////// + // PHASE 2: NOSTR EVENT VALIDATION (CPU Intensive ~2ms) + ///////////////////////////////////////////////////////////////////// + + // Check if authentication header is provided + if (!request->auth_header) { + + result->valid = 0; + result->error_code = NOSTR_ERROR_AUTH_REQUIRED; + strcpy(result->reason, "Authentication required but not provided"); + return NOSTR_SUCCESS; + } + char header_msg[110]; + sprintf(header_msg, + "VALIDATOR_DEBUG: STEP 4 PASSED - Auth header provided: %.50s...\n", + request->auth_header); + + // 4. Authorization Header Parsing - Extract base64-encoded Nostr event + // Format: "Authorization: Nostr " + // Early exit: Invalid base64 or malformed header rejected immediately + char event_json[4096]; + int parse_result = parse_authorization_header(request->auth_header, + event_json, sizeof(event_json)); + if (parse_result != NOSTR_SUCCESS) { + char parse_msg[256]; + sprintf(parse_msg, + "VALIDATOR_DEBUG: STEP 5 FAILED - Failed to parse authorization " + "header (error=%d)\n", + parse_result); + + result->valid = 0; + result->error_code = parse_result; + strcpy(result->reason, "Invalid authorization header format. Must be 'Nostr '"); + return NOSTR_SUCCESS; + } + char parse_success_msg[512]; + sprintf(parse_success_msg, + "VALIDATOR_DEBUG: STEP 5 PASSED - Authorization header parsed, JSON: " + "%.100s...\n", + event_json); + + // 5. JSON Parsing - Parse Nostr event JSON using cJSON + // Early exit: Invalid JSON rejected before signature verification + cJSON *event = cJSON_Parse(event_json); + if (!event) { + + result->valid = 0; + result->error_code = NOSTR_ERROR_EVENT_INVALID_CONTENT; + strcpy(result->reason, "Invalid JSON in authorization header. Ensure event is properly formatted JSON."); + return NOSTR_SUCCESS; + } + + // 6. Nostr Event Structure Validation - Validate required fields + // Early exit: Invalid structure rejected before expensive crypto + // operations + int validation_result = nostr_validate_event(event); + if (validation_result != NOSTR_SUCCESS) { + char validation_msg[256]; + sprintf(validation_msg, + "VALIDATOR_DEBUG: STEP 7 FAILED - NOSTR event validation failed " + "(error=%d)\n", + validation_result); + + result->valid = 0; + result->error_code = validation_result; + + // Map event validation errors to detailed messages + switch (validation_result) { + case NOSTR_ERROR_EVENT_INVALID_STRUCTURE: + strcpy(result->reason, "Event structure invalid. Missing required fields: id, pubkey, created_at, kind, tags, content, sig"); + break; + case NOSTR_ERROR_EVENT_INVALID_ID: + strcpy(result->reason, "Event ID verification failed. Check event serialization and hash calculation."); + break; + case NOSTR_ERROR_EVENT_INVALID_PUBKEY: + strcpy(result->reason, "Invalid pubkey format. Must be 64-character hex string."); + break; + case NOSTR_ERROR_EVENT_INVALID_SIGNATURE: + strcpy(result->reason, "Event signature verification failed. Check private key and signing process."); + break; + case NOSTR_ERROR_EVENT_INVALID_CREATED_AT: + strcpy(result->reason, "Invalid created_at timestamp. Must be valid Unix timestamp."); + break; + case NOSTR_ERROR_EVENT_INVALID_KIND: + strcpy(result->reason, "Invalid event kind. Must be valid integer."); + break; + case NOSTR_ERROR_EVENT_INVALID_TAGS: + strcpy(result->reason, "Invalid tags format. Tags must be array of string arrays."); + break; + case NOSTR_ERROR_EVENT_INVALID_CONTENT: + strcpy(result->reason, "Invalid content format. Content must be valid string."); + break; + default: + snprintf(result->reason, sizeof(result->reason), + "NOSTR event validation failed (error code: %d). Check event structure and format.", + validation_result); + break; + } + + cJSON_Delete(event); + return NOSTR_SUCCESS; + } + + // 11. Public Key Extraction (Both Paths) + // Extract validated public key for rule evaluation + char extracted_pubkey[65] = {0}; + int extract_result = extract_pubkey_from_event(event, extracted_pubkey, + sizeof(extracted_pubkey)); + if (extract_result != NOSTR_SUCCESS) { + char extract_msg[256]; + sprintf(extract_msg, + "VALIDATOR_DEBUG: STEP 8 FAILED - Failed to extract pubkey from " + "event (error=%d)\n", + extract_result); + + result->valid = 0; + result->error_code = extract_result; + strcpy(result->reason, "Failed to extract public key from event. Pubkey must be 64-character hex string."); + cJSON_Delete(event); + return NOSTR_SUCCESS; + } + char extract_success_msg[256]; + sprintf(extract_success_msg, + "VALIDATOR_DEBUG: STEP 8 PASSED - Extracted pubkey: %s\n", + extracted_pubkey); + + + ///////////////////////////////////////////////////////////////////// + // EVENT KIND ROUTING - Dual Authentication Support + ///////////////////////////////////////////////////////////////////// + // Kind 22242 (NIP-42): Route to NIP-42 challenge validation + // Kind 24242 (Blossom): Route to Blossom operation validation + // Other Kinds: Skip Nostr validation, proceed to rules + // Invalid Kind: Reject immediately + + // Get event kind to determine authentication method + cJSON *kind_json = cJSON_GetObjectItem(event, "kind"); + int event_kind = 0; + if (kind_json && cJSON_IsNumber(kind_json)) { + event_kind = cJSON_GetNumberValue(kind_json); + } + char kind_msg[256]; + sprintf(kind_msg, "VALIDATOR_DEBUG: STEP 9 PASSED - Event kind: %d\n", + event_kind); + + + ///////////////////////////////////////////////////////////////////// + // NIP42 + ///////////////////////////////////////////////////////////////////// + if (event_kind == NOSTR_NIP42_AUTH_EVENT_KIND) { + // 8. NIP-42 Challenge Validation (Kind 22242 Only ~500μs) + // Validate relay tag, verify challenge tag, check expiration + char nip42_msg[256]; + sprintf(nip42_msg, + "VALIDATOR_DEBUG: STEP 10 - Processing NIP-42 authentication (kind " + "%d)\n", + NOSTR_NIP42_AUTH_EVENT_KIND); + validator_debug_log(nip42_msg); + + // NIP-42 authentication (kind 22242) + if (!request->nip42_enabled || g_auth_cache.nip42_mode == 0) { + validator_debug_log("VALIDATOR_DEBUG: STEP 10 FAILED - NIP-42 " + "authentication is disabled\n"); + result->valid = 0; + result->error_code = NOSTR_ERROR_NIP42_DISABLED; + strcpy(result->reason, "NIP-42 authentication is disabled"); + cJSON_Delete(event); + return NOSTR_SUCCESS; + } + + // Extract challenge from event according to NIP-42 spec (tags only) + const char *challenge_for_validation = request->challenge_id; + if (!challenge_for_validation) { + // Look for challenge in tags (NIP-42 spec compliant) + cJSON *tags_json = cJSON_GetObjectItem(event, "tags"); + if (tags_json && cJSON_IsArray(tags_json)) { + cJSON *tag = NULL; + cJSON_ArrayForEach(tag, tags_json) { + if (!cJSON_IsArray(tag)) + continue; + + cJSON *tag_name = cJSON_GetArrayItem(tag, 0); + if (!tag_name || !cJSON_IsString(tag_name)) + continue; + + const char *tag_name_str = cJSON_GetStringValue(tag_name); + if (strcmp(tag_name_str, "challenge") == 0) { + cJSON *challenge_value = cJSON_GetArrayItem(tag, 1); + if (challenge_value && cJSON_IsString(challenge_value)) { + const char *challenge_from_tag = cJSON_GetStringValue(challenge_value); + if (challenge_from_tag && strlen(challenge_from_tag) > 0) { + // NIP-42 doesn't specify a fixed length, so accept any reasonable length + size_t challenge_len = strlen(challenge_from_tag); + if (challenge_len >= 8 && challenge_len <= 128) { // Reasonable bounds + // Basic validation - should be hex-like + int valid_chars = 1; + for (size_t i = 0; i < challenge_len; i++) { + char c = challenge_from_tag[i]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'))) { + valid_chars = 0; + break; + } + } + if (valid_chars) { + challenge_for_validation = challenge_from_tag; + char extract_msg[256]; + sprintf(extract_msg, + "VALIDATOR_DEBUG: STEP 10 - Extracted challenge from event " + "tag: %.16s...\n", + challenge_for_validation); + validator_debug_log(extract_msg); + break; // Found it, stop looking + } + } + } + } + } + } + } + } + + if (!request->request_url || !challenge_for_validation) { + validator_debug_log( + "VALIDATOR_DEBUG: STEP 10 FAILED - NIP-42 requires request_url and " + "challenge (from event content or parameter)\n"); + result->valid = 0; + result->error_code = NOSTR_ERROR_NIP42_NOT_CONFIGURED; + strcpy(result->reason, "NIP-42 authentication requires request_url and challenge"); + cJSON_Delete(event); + return NOSTR_SUCCESS; + } + + int nip42_result = validate_nip42_event(event, request->request_url, + challenge_for_validation); + if (nip42_result != NOSTR_SUCCESS) { + char nip42_fail_msg[256]; + sprintf(nip42_fail_msg, + "VALIDATOR_DEBUG: STEP 10 FAILED - NIP-42 validation failed " + "(error=%d)\n", + nip42_result); + validator_debug_log(nip42_fail_msg); + result->valid = 0; + result->error_code = nip42_result; + + // Map specific NIP-42 error codes to detailed error messages + switch (nip42_result) { + case NOSTR_ERROR_NIP42_CHALLENGE_NOT_FOUND: + strcpy(result->reason, "Challenge not found or has been used. Request a new challenge from /auth endpoint."); + break; + case NOSTR_ERROR_NIP42_CHALLENGE_EXPIRED: + strcpy(result->reason, "Challenge has expired. Request a new challenge from /auth endpoint."); + break; + case NOSTR_ERROR_NIP42_INVALID_CHALLENGE: + strcpy(result->reason, "Invalid challenge format. Challenge must be a valid hex string."); + break; + case NOSTR_ERROR_NIP42_URL_MISMATCH: + strcpy(result->reason, "Relay URL in auth event does not match server. Use 'ginxsom' as relay value."); + break; + case NOSTR_ERROR_NIP42_TIME_TOLERANCE: + strcpy(result->reason, "Auth event timestamp is outside acceptable time window. Check system clock."); + break; + case NOSTR_ERROR_NIP42_AUTH_EVENT_INVALID: + strcpy(result->reason, "NIP-42 auth event structure is invalid. Verify event format and required tags."); + break; + case NOSTR_ERROR_EVENT_INVALID_SIGNATURE: + strcpy(result->reason, "Event signature verification failed. Check private key and event serialization."); + break; + case NOSTR_ERROR_EVENT_INVALID_CONTENT: + strcpy(result->reason, "Event content is invalid. Challenge must be in event content field."); + break; + case NOSTR_ERROR_EVENT_INVALID_TAGS: + strcpy(result->reason, "Required tags missing. Auth event must include 'relay' and 'expiration' tags."); + break; + case NOSTR_ERROR_NIP42_INVALID_RELAY_URL: + strcpy(result->reason, "Invalid relay URL in tags. Use 'ginxsom' as the relay identifier."); + break; + case NOSTR_ERROR_NIP42_NOT_CONFIGURED: + strcpy(result->reason, "NIP-42 authentication not properly configured on server."); + break; + default: + snprintf(result->reason, sizeof(result->reason), + "NIP-42 authentication failed (error code: %d). Check event structure and signature.", + nip42_result); + break; + } + + cJSON_Delete(event); + return NOSTR_SUCCESS; + } + validator_debug_log( + "VALIDATOR_DEBUG: STEP 10 PASSED - NIP-42 authentication succeeded\n"); + strcpy(result->reason, "NIP-42 authentication passed"); + + } else if (event_kind == 24242) { + // 10. Operation-Specific Validation (Kind 24242 Only) + // Verify operation authorization, check required tags, validate + // expiration Early exit: Expired or mismatched events rejected + validator_debug_log("VALIDATOR_DEBUG: STEP 10 - Processing Blossom " + "authentication (kind 24242)\n"); + // Blossom protocol authentication (kind 24242) + if (request->operation && request->resource_hash) { + char blossom_valid_msg[512]; + sprintf(blossom_valid_msg, + "VALIDATOR_DEBUG: Validating Blossom event for operation='%s', " + "hash='%s'\n", + request->operation ? request->operation : "NULL", + request->resource_hash ? request->resource_hash : "NULL"); + validator_debug_log(blossom_valid_msg); + + int blossom_result = validate_blossom_event(event, request->resource_hash, + request->operation); + if (blossom_result != NOSTR_SUCCESS) { + char blossom_fail_msg[256]; + sprintf(blossom_fail_msg, + "VALIDATOR_DEBUG: STEP 10 FAILED - Blossom validation failed " + "(error=%d)\n", + blossom_result); + validator_debug_log(blossom_fail_msg); + result->valid = 0; + result->error_code = blossom_result; + + // Map specific Blossom error codes to detailed error messages + switch (blossom_result) { + case NOSTR_ERROR_EVENT_EXPIRED: + strcpy(result->reason, "Authorization event has expired. Create a new signed event with future expiration."); + break; + case NOSTR_ERROR_EVENT_INVALID_CONTENT: + strcpy(result->reason, "Event missing required tags. Blossom events need 't' (method) and 'x' (hash) tags."); + break; + case NOSTR_ERROR_EVENT_INVALID_TAGS: + strcpy(result->reason, "Invalid or missing Blossom tags. Check 't' tag matches operation and 'x' tag matches file hash."); + break; + case NOSTR_ERROR_EVENT_INVALID_SIGNATURE: + strcpy(result->reason, "Event signature verification failed. Check private key and event serialization."); + break; + case NOSTR_ERROR_EVENT_INVALID_KIND: + strcpy(result->reason, "Invalid event kind. Blossom authorization events must use kind 24242."); + break; + default: + snprintf(result->reason, sizeof(result->reason), + "Blossom event does not authorize this operation (error code: %d). Check tags and expiration.", + blossom_result); + break; + } + + cJSON_Delete(event); + return NOSTR_SUCCESS; + } + } else { + validator_debug_log("VALIDATOR_DEBUG: Skipping Blossom validation (no " + "operation/hash specified)\n"); + } + validator_debug_log( + "VALIDATOR_DEBUG: STEP 10 PASSED - Blossom authentication succeeded\n"); + strcpy(result->reason, "Blossom authentication passed"); + + } else { + char unsupported_msg[256]; + sprintf(unsupported_msg, + "VALIDATOR_DEBUG: STEP 10 FAILED - Unsupported event kind: %d\n", + event_kind); + validator_debug_log(unsupported_msg); + result->valid = 0; + result->error_code = NOSTR_ERROR_EVENT_INVALID_KIND; + snprintf(result->reason, sizeof(result->reason), + "Unsupported event kind %d for authentication. Use kind 22242 for NIP-42 or kind 24242 for Blossom.", + event_kind); + cJSON_Delete(event); + return NOSTR_SUCCESS; + } + + // Copy validated pubkey to result + if (strlen(extracted_pubkey) == 64) { + strncpy(result->pubkey, extracted_pubkey, 64); + result->pubkey[64] = '\0'; + validator_debug_log( + "VALIDATOR_DEBUG: STEP 11 PASSED - Pubkey copied to result\n"); + } else { + char pubkey_warning_msg[256]; + sprintf(pubkey_warning_msg, + "VALIDATOR_DEBUG: STEP 11 WARNING - Invalid pubkey length: %zu\n", + strlen(extracted_pubkey)); + validator_debug_log(pubkey_warning_msg); + } + + cJSON_Delete(event); + + // STEP 12 PASSED: Protocol validation complete - continue to database rule + // evaluation + validator_debug_log("VALIDATOR_DEBUG: STEP 12 PASSED - Protocol validation " + "complete, proceeding to rule evaluation\n"); + + ///////////////////////////////////////////////////////////////////// + // PHASE 3: AUTHENTICATION RULES (Database Queries ~500μs) + ///////////////////////////////////////////////////////////////////// + // 12. Rules System Check - Quick config check if auth rules enabled + // Early exit: If disabled, allow request immediately + + // Check if authentication is disabled first (regardless of header presence) + if (!g_auth_cache.auth_required) { + validator_debug_log("VALIDATOR_DEBUG: STEP 4 PASSED - Authentication " + "disabled, allowing request\n"); + strcpy(result->reason, "Authentication disabled"); + return NOSTR_SUCCESS; + } + + // 13. Cache Lookup - Check SQLite cache for previous decision + // Early exit: Cache hit returns cached decision (5-minute TTL ~100μs) + + ///////////////////////////////////////////////////////////////////// + // RULE EVALUATION ENGINE (Priority Order) + ///////////////////////////////////////////////////////////////////// + // a. Pubkey Blacklist (highest priority) - Immediate denial if matched + // b. Hash Blacklist - Block specific content hashes + // c. MIME Type Blacklist - Block dangerous file types + // d. File Size Limits - Enforce upload size restrictions + // e. Pubkey Whitelist - Allow specific users (only if not denied above) + // f. MIME Type Whitelist - Allow specific file types + + validator_debug_log("VALIDATOR_DEBUG: STEP 13 PASSED - Auth rules enabled, " + "checking database rules\n"); + + // Check database rules for authorization + int rules_result = check_database_auth_rules( + extracted_pubkey, request->operation, request->resource_hash); + if (rules_result != NOSTR_SUCCESS) { + validator_debug_log( + "VALIDATOR_DEBUG: STEP 14 FAILED - Database rules denied request\n"); + result->valid = 0; + result->error_code = rules_result; + // Determine specific failure reason based on rules evaluation + if (rules_result == NOSTR_ERROR_AUTH_REQUIRED) { + // This can be pubkey blacklist or whitelist violation - set generic + // message The specific reason will be detailed in the database check + // function + strcpy(result->reason, "Request denied by authorization rules"); + } else { + strcpy(result->reason, "Authorization error"); + } + return NOSTR_SUCCESS; + } + validator_debug_log( + "VALIDATOR_DEBUG: STEP 14 PASSED - Database rules allow request\n"); + + // 15. Whitelist Default Denial - If whitelist rules exist but none matched, + // deny + // Prevents whitelist bypass attacks + + // 16. Cache Storage - Store decision for future requests (5-minute TTL) + + // All validations passed + result->valid = 1; + result->error_code = NOSTR_SUCCESS; + validator_debug_log("VALIDATOR_DEBUG: STEP 15 PASSED - All validations " + "complete, request ALLOWED\n"); + return NOSTR_SUCCESS; } /** * Generate NIP-42 challenge for clients */ -int nostr_request_validator_generate_nip42_challenge(void* challenge_struct, const char* client_ip) { - // Mark client_ip as unused to suppress warning - it's for future enhancement - (void)client_ip; +int nostr_request_validator_generate_nip42_challenge(void *challenge_struct, + const char *client_ip) { + // Mark client_ip as unused to suppress warning - it's for future enhancement + (void)client_ip; - // Use nostr_core_lib NIP-42 functionality - char challenge_id[65]; - int result = nostr_nip42_generate_challenge(challenge_id, 32); - if (result != NOSTR_SUCCESS) { - return result; - } + // Use nostr_core_lib NIP-42 functionality + char challenge_id[65]; + int result = nostr_nip42_generate_challenge(challenge_id, 32); + if (result != NOSTR_SUCCESS) { + return result; + } - // Fill challenge structure (assuming it's a compatible structure) - // This is a simplified implementation - adjust based on actual structure needs - if (challenge_struct) { - // Cast to appropriate structure and fill fields - // For now, just return success - } - - return NOSTR_SUCCESS; + // Fill challenge structure (assuming it's a compatible structure) + // This is a simplified implementation - adjust based on actual structure + // needs + if (challenge_struct) { + // Cast to appropriate structure and fill fields + // For now, just return success + } + + return NOSTR_SUCCESS; } /** * Get the last rule violation type for status code mapping */ -const char* nostr_request_validator_get_last_violation_type(void) { - return g_last_rule_violation.violation_type; +const char *nostr_request_validator_get_last_violation_type(void) { + return g_last_rule_violation.violation_type; } /** * Clear the last rule violation details */ void nostr_request_validator_clear_violation(void) { - memset(&g_last_rule_violation, 0, sizeof(g_last_rule_violation)); + memset(&g_last_rule_violation, 0, sizeof(g_last_rule_violation)); } /** * Cleanup request validator resources */ -void nostr_request_validator_cleanup(void) { - g_validator_initialized = 0; - memset(&g_auth_cache, 0, sizeof(g_auth_cache)); - nostr_request_validator_clear_violation(); +void ginxsom_request_validator_cleanup(void) { + g_validator_initialized = 0; + memset(&g_auth_cache, 0, sizeof(g_auth_cache)); + nostr_request_validator_clear_violation(); } //============================================================================= @@ -428,420 +790,676 @@ void nostr_request_validator_cleanup(void) { * Get cache timeout from environment variable or default */ static int get_cache_timeout(void) { - char* no_cache = getenv("GINX_NO_CACHE"); - char* cache_timeout = getenv("GINX_CACHE_TIMEOUT"); - - if (no_cache && strcmp(no_cache, "1") == 0) { - return 0; // No caching - } - - if (cache_timeout) { - int timeout = atoi(cache_timeout); - return (timeout >= 0) ? timeout : 300; // Use provided value or default - } - - return 300; // Default 5 minutes + char *no_cache = getenv("GINX_NO_CACHE"); + char *cache_timeout = getenv("GINX_CACHE_TIMEOUT"); + + if (no_cache && strcmp(no_cache, "1") == 0) { + return 0; // No caching + } + + if (cache_timeout) { + int timeout = atoi(cache_timeout); + return (timeout >= 0) ? timeout : 300; // Use provided value or default + } + + return 300; // Default 5 minutes } /** * Force cache refresh - invalidates current cache */ void nostr_request_validator_force_cache_refresh(void) { - g_auth_cache.cache_valid = 0; - g_auth_cache.cache_expires = 0; - validator_debug_log("VALIDATOR: Cache forcibly invalidated\n"); + g_auth_cache.cache_valid = 0; + g_auth_cache.cache_expires = 0; + validator_debug_log("VALIDATOR: Cache forcibly invalidated\n"); } /** * Reload authentication configuration from unified config table */ static int reload_auth_config(void) { - sqlite3* db = NULL; - sqlite3_stmt* stmt = NULL; - int rc; - - // Clear cache - memset(&g_auth_cache, 0, sizeof(g_auth_cache)); - - // Open database - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc != SQLITE_OK) { - validator_debug_log("VALIDATOR: Could not open database\n"); - // Use defaults - g_auth_cache.auth_required = 0; - g_auth_cache.max_file_size = 104857600; // 100MB - g_auth_cache.admin_enabled = 0; - g_auth_cache.nip42_mode = 1; // Optional - int cache_timeout = get_cache_timeout(); - g_auth_cache.cache_expires = time(NULL) + cache_timeout; - g_auth_cache.cache_valid = 1; - return NOSTR_SUCCESS; - } - - // Load configuration values from unified config table - const char* config_sql = "SELECT key, value FROM config WHERE key IN ('require_auth', 'auth_rules_enabled', 'max_file_size', 'admin_enabled', 'admin_pubkey', 'require_nip42_auth')"; - rc = sqlite3_prepare_v2(db, config_sql, -1, &stmt, NULL); - - if (rc == 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) continue; - - if (strcmp(key, "require_auth") == 0) { - g_auth_cache.auth_required = (strcmp(value, "true") == 0) ? 1 : 0; - } else if (strcmp(key, "auth_rules_enabled") == 0) { - // Override auth_required with auth_rules_enabled if present (higher priority) - g_auth_cache.auth_required = (strcmp(value, "true") == 0) ? 1 : 0; - } else if (strcmp(key, "max_file_size") == 0) { - g_auth_cache.max_file_size = atol(value); - } else if (strcmp(key, "admin_enabled") == 0) { - g_auth_cache.admin_enabled = (strcmp(value, "true") == 0) ? 1 : 0; - } else if (strcmp(key, "admin_pubkey") == 0) { - strncpy(g_auth_cache.admin_pubkey, value, sizeof(g_auth_cache.admin_pubkey) - 1); - } else if (strcmp(key, "require_nip42_auth") == 0) { - if (strcmp(value, "false") == 0) { - g_auth_cache.nip42_mode = 0; - } else if (strcmp(value, "required") == 0) { - g_auth_cache.nip42_mode = 2; - } else { - g_auth_cache.nip42_mode = 1; // Optional - } - } - } - sqlite3_finalize(stmt); - } - - sqlite3_close(db); - - // Set cache expiration with environment variable support + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + int rc; + + // Clear cache + memset(&g_auth_cache, 0, sizeof(g_auth_cache)); + + // Open database + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc != SQLITE_OK) { + validator_debug_log("VALIDATOR: Could not open database\n"); + // Use defaults + g_auth_cache.auth_required = 0; + g_auth_cache.max_file_size = 104857600; // 100MB + g_auth_cache.admin_enabled = 0; + g_auth_cache.nip42_mode = 1; // Optional int cache_timeout = get_cache_timeout(); g_auth_cache.cache_expires = time(NULL) + cache_timeout; g_auth_cache.cache_valid = 1; - - // Set defaults for missing values - if (g_auth_cache.max_file_size == 0) { - g_auth_cache.max_file_size = 104857600; // 100MB - } - - // Debug logging - fprintf(stderr, "VALIDATOR: Configuration loaded from unified config table - auth_required: %d, max_file_size: %ld, nip42_mode: %d, cache_timeout: %d\n", - g_auth_cache.auth_required, g_auth_cache.max_file_size, g_auth_cache.nip42_mode, cache_timeout); - return NOSTR_SUCCESS; + } + + // Load configuration values from unified config table + const char *config_sql = + "SELECT key, value FROM config WHERE key IN ('require_auth', " + "'auth_rules_enabled', 'max_file_size', 'admin_enabled', 'admin_pubkey', " + "'nip42_require_auth', 'nip42_challenge_timeout', " + "'nip42_time_tolerance')"; + rc = sqlite3_prepare_v2(db, config_sql, -1, &stmt, NULL); + + if (rc == 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) + continue; + + if (strcmp(key, "require_auth") == 0) { + g_auth_cache.auth_required = (strcmp(value, "true") == 0) ? 1 : 0; + } else if (strcmp(key, "auth_rules_enabled") == 0) { + // Override auth_required with auth_rules_enabled if present (higher + // priority) + g_auth_cache.auth_required = (strcmp(value, "true") == 0) ? 1 : 0; + } else if (strcmp(key, "max_file_size") == 0) { + g_auth_cache.max_file_size = atol(value); + } else if (strcmp(key, "admin_enabled") == 0) { + g_auth_cache.admin_enabled = (strcmp(value, "true") == 0) ? 1 : 0; + } else if (strcmp(key, "admin_pubkey") == 0) { + strncpy(g_auth_cache.admin_pubkey, value, + sizeof(g_auth_cache.admin_pubkey) - 1); + } else if (strcmp(key, "nip42_require_auth") == 0) { + if (strcmp(value, "false") == 0) { + g_auth_cache.nip42_mode = 0; // Disabled + } else if (strcmp(value, "required") == 0) { + g_auth_cache.nip42_mode = 2; // Required + } else if (strcmp(value, "true") == 0) { + g_auth_cache.nip42_mode = 1; // Optional/Enabled + } else { + g_auth_cache.nip42_mode = 1; // Default to Optional/Enabled + } + } else if (strcmp(key, "nip42_challenge_timeout") == 0) { + g_auth_cache.nip42_challenge_timeout = atoi(value); + } else if (strcmp(key, "nip42_time_tolerance") == 0) { + g_auth_cache.nip42_time_tolerance = atoi(value); + } + } + sqlite3_finalize(stmt); + } + + sqlite3_close(db); + + // Set cache expiration with environment variable support + int cache_timeout = get_cache_timeout(); + g_auth_cache.cache_expires = time(NULL) + cache_timeout; + g_auth_cache.cache_valid = 1; + + // Set defaults for missing values + if (g_auth_cache.max_file_size == 0) { + g_auth_cache.max_file_size = 104857600; // 100MB + } + + // Debug logging + fprintf(stderr, + "VALIDATOR: Configuration loaded from unified config table - " + "auth_required: %d, max_file_size: %ld, nip42_mode: %d, " + "cache_timeout: %d\n", + g_auth_cache.auth_required, g_auth_cache.max_file_size, + g_auth_cache.nip42_mode, cache_timeout); + fprintf(stderr, + "VALIDATOR: NIP-42 mode details - nip42_mode=%d (0=disabled, " + "1=optional/enabled, 2=required)\n", + g_auth_cache.nip42_mode); + + return NOSTR_SUCCESS; } /** * Parse NOSTR authorization header (base64 decode) */ -static int parse_authorization_header(const char* auth_header, char* event_json, size_t json_size) { - if (!auth_header || !event_json) { - return NOSTR_ERROR_INVALID_INPUT; - } - - // Check for "Nostr " prefix (case-insensitive) - const char* prefix = "nostr "; - size_t prefix_len = strlen(prefix); - - if (strncasecmp(auth_header, prefix, prefix_len) != 0) { - return NOSTR_ERROR_INVALID_INPUT; - } - - // Extract base64 encoded event after "Nostr " - const char* base64_event = auth_header + prefix_len; - - // Decode base64 to JSON using nostr_core_lib base64 decode - unsigned char decoded_buffer[4096]; - size_t decoded_len = base64_decode(base64_event, decoded_buffer); - - if (decoded_len == 0 || decoded_len >= json_size) { - return NOSTR_ERROR_INVALID_INPUT; - } - - // Copy decoded JSON to output buffer - memcpy(event_json, decoded_buffer, decoded_len); - event_json[decoded_len] = '\0'; - - return NOSTR_SUCCESS; +static int parse_authorization_header(const char *auth_header, char *event_json, + size_t json_size) { + if (!auth_header || !event_json) { + return NOSTR_ERROR_INVALID_INPUT; + } + + // Check for "Nostr " prefix (case-insensitive) + const char *prefix = "nostr "; + size_t prefix_len = strlen(prefix); + + if (strncasecmp(auth_header, prefix, prefix_len) != 0) { + return NOSTR_ERROR_INVALID_INPUT; + } + + // Extract base64 encoded event after "Nostr " + const char *base64_event = auth_header + prefix_len; + + // Decode base64 to JSON using nostr_core_lib base64 decode + unsigned char decoded_buffer[4096]; + size_t decoded_len = base64_decode(base64_event, decoded_buffer); + + if (decoded_len == 0 || decoded_len >= json_size) { + return NOSTR_ERROR_INVALID_INPUT; + } + + // Copy decoded JSON to output buffer + memcpy(event_json, decoded_buffer, decoded_len); + event_json[decoded_len] = '\0'; + + return NOSTR_SUCCESS; } /** * Extract pubkey from validated NOSTR event */ -static int extract_pubkey_from_event(cJSON* event, char* pubkey_buffer, size_t buffer_size) { - if (!event || !pubkey_buffer || buffer_size < 65) { - return NOSTR_ERROR_INVALID_INPUT; +static int extract_pubkey_from_event(cJSON *event, char *pubkey_buffer, + size_t buffer_size) { + if (!event || !pubkey_buffer || buffer_size < 65) { + return NOSTR_ERROR_INVALID_INPUT; + } + + // Initialize buffer to prevent corruption + memset(pubkey_buffer, 0, buffer_size); + + cJSON *pubkey_json = cJSON_GetObjectItem(event, "pubkey"); + if (!pubkey_json || !cJSON_IsString(pubkey_json)) { + return NOSTR_ERROR_EVENT_INVALID_PUBKEY; + } + + const char *pubkey = cJSON_GetStringValue(pubkey_json); + if (!pubkey) { + return NOSTR_ERROR_EVENT_INVALID_PUBKEY; + } + + // Check the raw pubkey string before validation + size_t pubkey_len = strlen(pubkey); + if (pubkey_len != 64) { + return NOSTR_ERROR_EVENT_INVALID_PUBKEY; + } + + // Validate that pubkey contains only hex characters before copying + for (int i = 0; i < 64; i++) { + char c = pubkey[i]; + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'))) { + return NOSTR_ERROR_EVENT_INVALID_PUBKEY; } - - // Initialize buffer to prevent corruption - memset(pubkey_buffer, 0, buffer_size); - - cJSON* pubkey_json = cJSON_GetObjectItem(event, "pubkey"); - if (!pubkey_json || !cJSON_IsString(pubkey_json)) { - return NOSTR_ERROR_EVENT_INVALID_PUBKEY; - } - - const char* pubkey = cJSON_GetStringValue(pubkey_json); - if (!pubkey) { - return NOSTR_ERROR_EVENT_INVALID_PUBKEY; - } - - // Check the raw pubkey string before validation - size_t pubkey_len = strlen(pubkey); - if (pubkey_len != 64) { - return NOSTR_ERROR_EVENT_INVALID_PUBKEY; - } - - // Validate that pubkey contains only hex characters before copying - for (int i = 0; i < 64; i++) { - char c = pubkey[i]; - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { - return NOSTR_ERROR_EVENT_INVALID_PUBKEY; - } - } - - // Safe copy with explicit length and null termination - memcpy(pubkey_buffer, pubkey, 64); - pubkey_buffer[64] = '\0'; - - return NOSTR_SUCCESS; + } + + // Safe copy with explicit length and null termination + memcpy(pubkey_buffer, pubkey, 64); + pubkey_buffer[64] = '\0'; + + return NOSTR_SUCCESS; } /** * Validate Blossom protocol event (kind 24242) */ -static int validate_blossom_event(cJSON* event, const char* expected_hash, const char* method) { - if (!event) { - return NOSTR_ERROR_INVALID_INPUT; +static int validate_blossom_event(cJSON *event, const char *expected_hash, + const char *method) { + if (!event) { + return NOSTR_ERROR_INVALID_INPUT; + } + + // Check event kind (must be 24242 for Blossom operations) + cJSON *kind_json = cJSON_GetObjectItem(event, "kind"); + if (!kind_json || !cJSON_IsNumber(kind_json)) { + return NOSTR_ERROR_EVENT_INVALID_CONTENT; + } + + int kind = cJSON_GetNumberValue(kind_json); + if (kind != 24242) { + return NOSTR_ERROR_EVENT_INVALID_CONTENT; + } + + // Look for required tags if method and hash are specified + if (method || expected_hash) { + cJSON *tags = cJSON_GetObjectItem(event, "tags"); + if (!tags || !cJSON_IsArray(tags)) { + return NOSTR_ERROR_EVENT_INVALID_CONTENT; } - - // Check event kind (must be 24242 for Blossom operations) - cJSON* kind_json = cJSON_GetObjectItem(event, "kind"); - if (!kind_json || !cJSON_IsNumber(kind_json)) { - return NOSTR_ERROR_EVENT_INVALID_CONTENT; + + int found_method = (method == NULL); + int found_hash = (expected_hash == NULL); + time_t expiration = 0; + + cJSON *tag = NULL; + cJSON_ArrayForEach(tag, tags) { + if (!cJSON_IsArray(tag)) + continue; + + cJSON *tag_name = cJSON_GetArrayItem(tag, 0); + if (!tag_name || !cJSON_IsString(tag_name)) + continue; + + const char *tag_name_str = cJSON_GetStringValue(tag_name); + + if (strcmp(tag_name_str, "t") == 0 && method) { + cJSON *method_value = cJSON_GetArrayItem(tag, 1); + if (method_value && cJSON_IsString(method_value)) { + const char *event_method = cJSON_GetStringValue(method_value); + if (strcmp(event_method, method) == 0) { + found_method = 1; + } + } + } else if (strcmp(tag_name_str, "x") == 0 && expected_hash) { + cJSON *hash_value = cJSON_GetArrayItem(tag, 1); + if (hash_value && cJSON_IsString(hash_value)) { + const char *event_hash = cJSON_GetStringValue(hash_value); + if (strcmp(event_hash, expected_hash) == 0) { + found_hash = 1; + } + } + } else if (strcmp(tag_name_str, "expiration") == 0) { + cJSON *exp_value = cJSON_GetArrayItem(tag, 1); + if (exp_value && cJSON_IsString(exp_value)) { + expiration = (time_t)atol(cJSON_GetStringValue(exp_value)); + } + } } - - int kind = cJSON_GetNumberValue(kind_json); - if (kind != 24242) { - return NOSTR_ERROR_EVENT_INVALID_CONTENT; + + if (!found_method || !found_hash) { + return NOSTR_ERROR_EVENT_INVALID_CONTENT; } - - // Look for required tags if method and hash are specified - if (method || expected_hash) { - cJSON* tags = cJSON_GetObjectItem(event, "tags"); - if (!tags || !cJSON_IsArray(tags)) { - return NOSTR_ERROR_EVENT_INVALID_CONTENT; - } - - int found_method = (method == NULL); - int found_hash = (expected_hash == NULL); - time_t expiration = 0; - - cJSON* tag = NULL; - cJSON_ArrayForEach(tag, tags) { - if (!cJSON_IsArray(tag)) continue; - - cJSON* tag_name = cJSON_GetArrayItem(tag, 0); - if (!tag_name || !cJSON_IsString(tag_name)) continue; - - const char* tag_name_str = cJSON_GetStringValue(tag_name); - - if (strcmp(tag_name_str, "t") == 0 && method) { - cJSON* method_value = cJSON_GetArrayItem(tag, 1); - if (method_value && cJSON_IsString(method_value)) { - const char* event_method = cJSON_GetStringValue(method_value); - if (strcmp(event_method, method) == 0) { - found_method = 1; - } - } - } else if (strcmp(tag_name_str, "x") == 0 && expected_hash) { - cJSON* hash_value = cJSON_GetArrayItem(tag, 1); - if (hash_value && cJSON_IsString(hash_value)) { - const char* event_hash = cJSON_GetStringValue(hash_value); - if (strcmp(event_hash, expected_hash) == 0) { - found_hash = 1; - } - } - } else if (strcmp(tag_name_str, "expiration") == 0) { - cJSON* exp_value = cJSON_GetArrayItem(tag, 1); - if (exp_value && cJSON_IsString(exp_value)) { - expiration = (time_t)atol(cJSON_GetStringValue(exp_value)); - } - } - } - - if (!found_method || !found_hash) { - return NOSTR_ERROR_EVENT_INVALID_CONTENT; - } - - // Check expiration - time_t now = time(NULL); - if (expiration > 0 && now > expiration) { - return NOSTR_ERROR_EVENT_EXPIRED; - } + + // Check expiration + time_t now = time(NULL); + if (expiration > 0 && now > expiration) { + return NOSTR_ERROR_EVENT_EXPIRED; } - - return NOSTR_SUCCESS; + } + + return NOSTR_SUCCESS; } /** * Check database authentication rules for the request * Implements the 6-step rule evaluation engine from AUTH_API.md */ -static int check_database_auth_rules(const char* pubkey, const char* operation, const char* resource_hash) { - sqlite3* db = NULL; - sqlite3_stmt* stmt = NULL; - int rc; - - if (!pubkey) { - validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - Missing pubkey for rule evaluation\n"); - return NOSTR_ERROR_INVALID_INPUT; +static int check_database_auth_rules(const char *pubkey, const char *operation, + const char *resource_hash) { + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + int rc; + + if (!pubkey) { + validator_debug_log( + "VALIDATOR_DEBUG: RULES ENGINE - Missing pubkey for rule evaluation\n"); + return NOSTR_ERROR_INVALID_INPUT; + } + + char rules_msg[256]; + sprintf(rules_msg, + "VALIDATOR_DEBUG: RULES ENGINE - Checking rules for pubkey=%.32s..., " + "operation=%s\n", + pubkey, operation ? operation : "NULL"); + validator_debug_log(rules_msg); + + // Open database + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc != SQLITE_OK) { + validator_debug_log( + "VALIDATOR_DEBUG: RULES ENGINE - Failed to open database\n"); + return NOSTR_SUCCESS; // Default allow on DB error + } + + // Step 1: Check pubkey blacklist (highest priority) + const char *blacklist_sql = + "SELECT rule_type, description FROM auth_rules WHERE rule_type = " + "'pubkey_blacklist' AND rule_target = ? AND operation = ? AND enabled = " + "1 ORDER BY priority LIMIT 1"; + rc = sqlite3_prepare_v2(db, blacklist_sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC); + + if (sqlite3_step(stmt) == SQLITE_ROW) { + const char *description = (const char *)sqlite3_column_text(stmt, 1); + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 1 FAILED - " + "Pubkey blacklisted\n"); + char blacklist_msg[256]; + sprintf(blacklist_msg, + "VALIDATOR_DEBUG: RULES ENGINE - Blacklist rule matched: %s\n", + description ? description : "Unknown"); + validator_debug_log(blacklist_msg); + + // Set specific violation details for status code mapping + strcpy(g_last_rule_violation.violation_type, "pubkey_blacklist"); + sprintf(g_last_rule_violation.reason, "%s: Public key blacklisted", + description ? description : "TEST_PUBKEY_BLACKLIST"); + + sqlite3_finalize(stmt); + sqlite3_close(db); + return NOSTR_ERROR_AUTH_REQUIRED; } - - char rules_msg[256]; - sprintf(rules_msg, "VALIDATOR_DEBUG: RULES ENGINE - Checking rules for pubkey=%.32s..., operation=%s\n", - pubkey, operation ? operation : "NULL"); - validator_debug_log(rules_msg); - - // Open database - rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); - if (rc != SQLITE_OK) { - validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - Failed to open database\n"); - return NOSTR_SUCCESS; // Default allow on DB error - } - - // Step 1: Check pubkey blacklist (highest priority) - const char* blacklist_sql = "SELECT rule_type, description FROM auth_rules WHERE rule_type = 'pubkey_blacklist' AND rule_target = ? AND operation = ? AND enabled = 1 ORDER BY priority LIMIT 1"; - rc = sqlite3_prepare_v2(db, blacklist_sql, -1, &stmt, NULL); + sqlite3_finalize(stmt); + } + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 1 PASSED - Pubkey " + "not blacklisted\n"); + + // Step 2: Check hash blacklist + if (resource_hash) { + const char *hash_blacklist_sql = + "SELECT rule_type, description FROM auth_rules WHERE rule_type = " + "'hash_blacklist' AND rule_target = ? AND operation = ? AND enabled = " + "1 ORDER BY priority LIMIT 1"; + rc = sqlite3_prepare_v2(db, hash_blacklist_sql, -1, &stmt, NULL); if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC); - - if (sqlite3_step(stmt) == SQLITE_ROW) { - const char* description = (const char*)sqlite3_column_text(stmt, 1); - validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 1 FAILED - Pubkey blacklisted\n"); - char blacklist_msg[256]; - sprintf(blacklist_msg, "VALIDATOR_DEBUG: RULES ENGINE - Blacklist rule matched: %s\n", description ? description : "Unknown"); - validator_debug_log(blacklist_msg); + sqlite3_bind_text(stmt, 1, resource_hash, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC); - // Set specific violation details for status code mapping - strcpy(g_last_rule_violation.violation_type, "pubkey_blacklist"); - sprintf(g_last_rule_violation.reason, "%s: Public key blacklisted", description ? description : "TEST_PUBKEY_BLACKLIST"); + if (sqlite3_step(stmt) == SQLITE_ROW) { + const char *description = (const char *)sqlite3_column_text(stmt, 1); + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 FAILED - " + "Hash blacklisted\n"); + char hash_blacklist_msg[256]; + sprintf( + hash_blacklist_msg, + "VALIDATOR_DEBUG: RULES ENGINE - Hash blacklist rule matched: %s\n", + description ? description : "Unknown"); + validator_debug_log(hash_blacklist_msg); + + // Set specific violation details for status code mapping + strcpy(g_last_rule_violation.violation_type, "hash_blacklist"); + sprintf(g_last_rule_violation.reason, "%s: File hash blacklisted", + description ? description : "TEST_HASH_BLACKLIST"); - sqlite3_finalize(stmt); - sqlite3_close(db); - return NOSTR_ERROR_AUTH_REQUIRED; - } sqlite3_finalize(stmt); + sqlite3_close(db); + return NOSTR_ERROR_AUTH_REQUIRED; + } + sqlite3_finalize(stmt); } - validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 1 PASSED - Pubkey not blacklisted\n"); - - // Step 2: Check hash blacklist - if (resource_hash) { - const char* hash_blacklist_sql = "SELECT rule_type, description FROM auth_rules WHERE rule_type = 'hash_blacklist' AND rule_target = ? AND operation = ? AND enabled = 1 ORDER BY priority LIMIT 1"; - rc = sqlite3_prepare_v2(db, hash_blacklist_sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, resource_hash, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC); - - if (sqlite3_step(stmt) == SQLITE_ROW) { - const char* description = (const char*)sqlite3_column_text(stmt, 1); - validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 FAILED - Hash blacklisted\n"); - char hash_blacklist_msg[256]; - sprintf(hash_blacklist_msg, "VALIDATOR_DEBUG: RULES ENGINE - Hash blacklist rule matched: %s\n", description ? description : "Unknown"); - validator_debug_log(hash_blacklist_msg); + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 PASSED - Hash " + "not blacklisted\n"); + } else { + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 SKIPPED - No " + "resource hash provided\n"); + } - // Set specific violation details for status code mapping - strcpy(g_last_rule_violation.violation_type, "hash_blacklist"); - sprintf(g_last_rule_violation.reason, "%s: File hash blacklisted", description ? description : "TEST_HASH_BLACKLIST"); + // Step 3: Check pubkey whitelist + const char *whitelist_sql = + "SELECT rule_type, description FROM auth_rules WHERE rule_type = " + "'pubkey_whitelist' AND rule_target = ? AND operation = ? AND enabled = " + "1 ORDER BY priority LIMIT 1"; + rc = sqlite3_prepare_v2(db, whitelist_sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC); - sqlite3_finalize(stmt); - sqlite3_close(db); - return NOSTR_ERROR_AUTH_REQUIRED; - } - sqlite3_finalize(stmt); - } - validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 PASSED - Hash not blacklisted\n"); - } else { - validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 SKIPPED - No resource hash provided\n"); + if (sqlite3_step(stmt) == SQLITE_ROW) { + const char *description = (const char *)sqlite3_column_text(stmt, 1); + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 PASSED - " + "Pubkey whitelisted\n"); + char whitelist_msg[256]; + sprintf(whitelist_msg, + "VALIDATOR_DEBUG: RULES ENGINE - Whitelist rule matched: %s\n", + description ? description : "Unknown"); + validator_debug_log(whitelist_msg); + sqlite3_finalize(stmt); + sqlite3_close(db); + return NOSTR_SUCCESS; // Allow whitelisted pubkey } - - // Step 3: Check pubkey whitelist - const char* whitelist_sql = "SELECT rule_type, description FROM auth_rules WHERE rule_type = 'pubkey_whitelist' AND rule_target = ? AND operation = ? AND enabled = 1 ORDER BY priority LIMIT 1"; - rc = sqlite3_prepare_v2(db, whitelist_sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, operation ? operation : "", -1, SQLITE_STATIC); - - if (sqlite3_step(stmt) == SQLITE_ROW) { - const char* description = (const char*)sqlite3_column_text(stmt, 1); - validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 PASSED - Pubkey whitelisted\n"); - char whitelist_msg[256]; - sprintf(whitelist_msg, "VALIDATOR_DEBUG: RULES ENGINE - Whitelist rule matched: %s\n", description ? description : "Unknown"); - validator_debug_log(whitelist_msg); - sqlite3_finalize(stmt); - sqlite3_close(db); - return NOSTR_SUCCESS; // Allow whitelisted pubkey - } + sqlite3_finalize(stmt); + } + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 FAILED - Pubkey " + "not whitelisted\n"); + + // Step 4: Check if any whitelist rules exist - if yes, deny by default + const char *whitelist_exists_sql = + "SELECT COUNT(*) FROM auth_rules WHERE rule_type = 'pubkey_whitelist' " + "AND operation = ? AND enabled = 1 LIMIT 1"; + rc = sqlite3_prepare_v2(db, whitelist_exists_sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, operation ? operation : "", -1, SQLITE_STATIC); + + if (sqlite3_step(stmt) == SQLITE_ROW) { + int whitelist_count = sqlite3_column_int(stmt, 0); + if (whitelist_count > 0) { + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 4 FAILED - " + "Whitelist exists but pubkey not in it\n"); + + // Set specific violation details for status code mapping + strcpy(g_last_rule_violation.violation_type, "whitelist_violation"); + strcpy(g_last_rule_violation.reason, + "Public key not whitelisted for this operation"); + sqlite3_finalize(stmt); + sqlite3_close(db); + return NOSTR_ERROR_AUTH_REQUIRED; + } } - validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 FAILED - Pubkey not whitelisted\n"); - - // Step 4: Check if any whitelist rules exist - if yes, deny by default - const char* whitelist_exists_sql = "SELECT COUNT(*) FROM auth_rules WHERE rule_type = 'pubkey_whitelist' AND operation = ? AND enabled = 1 LIMIT 1"; - rc = sqlite3_prepare_v2(db, whitelist_exists_sql, -1, &stmt, NULL); - if (rc == SQLITE_OK) { - sqlite3_bind_text(stmt, 1, operation ? operation : "", -1, SQLITE_STATIC); - - if (sqlite3_step(stmt) == SQLITE_ROW) { - int whitelist_count = sqlite3_column_int(stmt, 0); - if (whitelist_count > 0) { - validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 4 FAILED - Whitelist exists but pubkey not in it\n"); + sqlite3_finalize(stmt); + } + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 4 PASSED - No " + "whitelist restrictions apply\n"); - // Set specific violation details for status code mapping - strcpy(g_last_rule_violation.violation_type, "whitelist_violation"); - strcpy(g_last_rule_violation.reason, "Public key not whitelisted for this operation"); - - sqlite3_finalize(stmt); - sqlite3_close(db); - return NOSTR_ERROR_AUTH_REQUIRED; - } - } - sqlite3_finalize(stmt); - } - validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 4 PASSED - No whitelist restrictions apply\n"); - - sqlite3_close(db); - validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 5 PASSED - All rule checks completed, default ALLOW\n"); - return NOSTR_SUCCESS; // Default allow if no restrictive rules matched + sqlite3_close(db); + validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 5 PASSED - All " + "rule checks completed, default ALLOW\n"); + return NOSTR_SUCCESS; // Default allow if no restrictive rules matched } /** * Validate NIP-42 authentication event (kind 22242) */ -static int validate_nip42_event(cJSON* event, const char* relay_url, const char* challenge_id) { - if (!event || !relay_url || !challenge_id) { - return NOSTR_ERROR_INVALID_INPUT; +static int validate_nip42_event(cJSON *event, const char *relay_url, + const char *challenge_id) { + if (!event || !relay_url || !challenge_id) { + return NOSTR_ERROR_INVALID_INPUT; + } + + // Check event kind (must be 22242 for NIP-42) + cJSON *kind_json = cJSON_GetObjectItem(event, "kind"); + if (!kind_json || !cJSON_IsNumber(kind_json)) { + return NOSTR_ERROR_EVENT_INVALID_CONTENT; + } + + int kind = cJSON_GetNumberValue(kind_json); + if (kind != NOSTR_NIP42_AUTH_EVENT_KIND) { + return NOSTR_ERROR_EVENT_INVALID_CONTENT; + } + + // Validate that the challenge exists and is not expired + int challenge_result = validate_challenge(challenge_id); + if (challenge_result != NOSTR_SUCCESS) { + return challenge_result; + } + + // Use the existing NIP-42 verification from nostr_core_lib + int verification_result = + nostr_nip42_verify_auth_event(event, challenge_id, relay_url, + g_challenge_manager.time_tolerance_seconds); + if (verification_result != NOSTR_SUCCESS) { + return verification_result; + } + + return NOSTR_SUCCESS; +} + +//============================================================================= +// NIP-42 CHALLENGE MANAGEMENT FUNCTIONS +//============================================================================= + +/** + * Generate a challenge ID using nostr_core_lib + */ +static int generate_challenge_id(char *challenge_buffer, size_t buffer_size) { + if (!challenge_buffer || buffer_size < 65) { + return NOSTR_ERROR_INVALID_INPUT; + } + + // Use nostr_core_lib to generate a random challenge + return nostr_nip42_generate_challenge(challenge_buffer, 32); +} + +/** + * Clean up expired challenges from memory + */ +static void cleanup_expired_challenges(void) { + time_t now = time(NULL); + + // Only cleanup if enough time has passed + if (now - g_challenge_manager.last_cleanup < CHALLENGE_CLEANUP_INTERVAL) { + return; + } + + int active_count = 0; + for (int i = 0; i < g_challenge_manager.challenge_count; i++) { + if (g_challenge_manager.challenges[i].active) { + if (now > g_challenge_manager.challenges[i].expires_at) { + // Mark expired challenge as inactive + g_challenge_manager.challenges[i].active = 0; + memset(g_challenge_manager.challenges[i].challenge_id, 0, + sizeof(g_challenge_manager.challenges[i].challenge_id)); + } else { + active_count++; + } } - - // Check event kind (must be 22242 for NIP-42) - cJSON* kind_json = cJSON_GetObjectItem(event, "kind"); - if (!kind_json || !cJSON_IsNumber(kind_json)) { - return NOSTR_ERROR_EVENT_INVALID_CONTENT; + } + + // Compact the array if we have many inactive entries + if (active_count < g_challenge_manager.challenge_count / 2 && + active_count < MAX_CHALLENGES - 100) { + int write_idx = 0; + for (int read_idx = 0; read_idx < g_challenge_manager.challenge_count; + read_idx++) { + if (g_challenge_manager.challenges[read_idx].active) { + if (write_idx != read_idx) { + memcpy(&g_challenge_manager.challenges[write_idx], + &g_challenge_manager.challenges[read_idx], + sizeof(nip42_challenge_entry_t)); + } + write_idx++; + } } - - int kind = cJSON_GetNumberValue(kind_json); - if (kind != NOSTR_NIP42_AUTH_EVENT_KIND) { - return NOSTR_ERROR_EVENT_INVALID_CONTENT; + g_challenge_manager.challenge_count = write_idx; + } + + g_challenge_manager.last_cleanup = now; + + char cleanup_msg[256]; + sprintf(cleanup_msg, "NIP-42: Cleaned up challenges, %d active remaining\n", + active_count); + validator_debug_log(cleanup_msg); +} + +/** + * Store a new challenge in memory + */ +static int store_challenge(const char *challenge_id, const char *client_ip) { + if (!challenge_id || strlen(challenge_id) == 0) { + return NOSTR_ERROR_INVALID_INPUT; + } + + cleanup_expired_challenges(); + + // Find an available slot + int slot_idx = -1; + + // First, try to find an inactive slot + for (int i = 0; i < g_challenge_manager.challenge_count; i++) { + if (!g_challenge_manager.challenges[i].active) { + slot_idx = i; + break; } - - // Use the existing NIP-42 verification from nostr_core_lib - int verification_result = nostr_nip42_verify_auth_event(event, challenge_id, - relay_url, NOSTR_NIP42_DEFAULT_TIME_TOLERANCE); - if (verification_result != NOSTR_SUCCESS) { - return verification_result; + } + + // If no inactive slot found, use next available if we haven't hit max + if (slot_idx == -1 && g_challenge_manager.challenge_count < MAX_CHALLENGES) { + slot_idx = g_challenge_manager.challenge_count++; + } + + // If still no slot, we're full - remove oldest entry + if (slot_idx == -1) { + slot_idx = 0; // Overwrite first entry (oldest) + } + + // Store the new challenge + nip42_challenge_entry_t *entry = &g_challenge_manager.challenges[slot_idx]; + memset(entry, 0, sizeof(nip42_challenge_entry_t)); + + // Store challenge with proper length handling (up to buffer size - 1) + strncpy(entry->challenge_id, challenge_id, sizeof(entry->challenge_id) - 1); + entry->challenge_id[sizeof(entry->challenge_id) - 1] = '\0'; + + if (client_ip) { + strncpy(entry->client_ip, client_ip, sizeof(entry->client_ip) - 1); + entry->client_ip[sizeof(entry->client_ip) - 1] = '\0'; + } + + time_t now = time(NULL); + entry->created_at = now; + entry->expires_at = now + g_challenge_manager.timeout_seconds; + entry->active = 1; + + char store_msg[256]; + sprintf(store_msg, + "NIP-42: Stored challenge %.16s... (expires in %d seconds)\n", + challenge_id, g_challenge_manager.timeout_seconds); + validator_debug_log(store_msg); + + return NOSTR_SUCCESS; +} + +/** + * Validate that a challenge exists and is not expired + */ +static int validate_challenge(const char *challenge_id) { + if (!challenge_id || strlen(challenge_id) == 0) { + return NOSTR_ERROR_INVALID_INPUT; + } + + cleanup_expired_challenges(); + + time_t now = time(NULL); + + for (int i = 0; i < g_challenge_manager.challenge_count; i++) { + nip42_challenge_entry_t *entry = &g_challenge_manager.challenges[i]; + + if (entry->active && strcmp(entry->challenge_id, challenge_id) == 0) { + if (now <= entry->expires_at) { + char validate_msg[256]; + sprintf(validate_msg, + "NIP-42: Challenge %.16s... validated successfully\n", + challenge_id); + validator_debug_log(validate_msg); + return NOSTR_SUCCESS; + } else { + // Mark as expired + entry->active = 0; + validator_debug_log("NIP-42: Challenge found but expired\n"); + return NOSTR_ERROR_NIP42_CHALLENGE_EXPIRED; + } } - - return NOSTR_SUCCESS; -} \ No newline at end of file + } + + validator_debug_log("NIP-42: Challenge not found\n"); + return NOSTR_ERROR_NIP42_CHALLENGE_NOT_FOUND; +} + +/** + * Generate and store a new NIP-42 challenge for /auth endpoint + */ +int nostr_generate_nip42_challenge(char *challenge_out, size_t challenge_size, + const char *client_ip) { + if (!challenge_out || challenge_size < 65) { + return NOSTR_ERROR_INVALID_INPUT; + } + + // Generate challenge ID + int result = generate_challenge_id(challenge_out, challenge_size); + if (result != NOSTR_SUCCESS) { + return result; + } + + // Store in challenge manager + result = store_challenge(challenge_out, client_ip); + if (result != NOSTR_SUCCESS) { + return result; + } + + return NOSTR_SUCCESS; +} diff --git a/tests/auth_test.sh b/tests/auth_test.sh index 4b3a559..9fa9dd4 100755 --- a/tests/auth_test.sh +++ b/tests/auth_test.sh @@ -407,12 +407,12 @@ test_nip42_authentication() { # Test NIP-42 configuration modes test_nip42_configuration() { - # Check NIP-42 mode in database using unified config table - local nip42_mode=$(sqlite3 "$DB_PATH" "SELECT value FROM config WHERE key = 'require_nip42_auth';" 2>/dev/null || echo "") + # Check NIP-42 mode in database using unified config table (updated key name) + local nip42_mode=$(sqlite3 "$DB_PATH" "SELECT value FROM config WHERE key = 'nip42_require_auth';" 2>/dev/null || echo "") if [[ -n "$nip42_mode" ]]; then case "$nip42_mode" in - "true"|"false") + "true"|"false"|"optional"|"required"|"disabled") record_test_result "NIP-42 Configuration Check" "VALID" "VALID" "true" ;; *) @@ -422,6 +422,16 @@ test_nip42_configuration() { else record_test_result "NIP-42 Configuration Check" "VALID" "DEFAULT" "true" fi + + # Also check that the other NIP-42 config keys exist + local timeout=$(sqlite3 "$DB_PATH" "SELECT value FROM config WHERE key = 'nip42_challenge_timeout';" 2>/dev/null || echo "") + local tolerance=$(sqlite3 "$DB_PATH" "SELECT value FROM config WHERE key = 'nip42_time_tolerance';" 2>/dev/null || echo "") + + if [[ -n "$timeout" && -n "$tolerance" ]]; then + record_test_result "NIP-42 Config Keys Check" "VALID" "VALID" "true" + else + record_test_result "NIP-42 Config Keys Check" "VALID" "PARTIAL" "true" + fi } # Test dual authentication capability diff --git a/tests/auth_test_tmp/nip42_test.txt b/tests/auth_test_tmp/nip42_test.txt index 156226a..f028755 100644 --- a/tests/auth_test_tmp/nip42_test.txt +++ b/tests/auth_test_tmp/nip42_test.txt @@ -1 +1 @@ -NIP-42 authentication test content +NIP-42 test content diff --git a/tests/mirror_test_bud04.sh b/tests/mirror_test_bud04.sh index a7fb8f6..7045bd2 100755 --- a/tests/mirror_test_bud04.sh +++ b/tests/mirror_test_bud04.sh @@ -1,17 +1,34 @@ #!/bin/bash # Mirror Test Script for BUD-04 -# Tests the PUT /mirror endpoint with a sample PNG file +# Tests the PUT /mirror endpoint with a sample PNG file and NIP-42 authentication # Test URL - PNG file with known SHA-256 hash TEST_URL="https://laantungir.github.io/img_repo/24308d48eb498b593e55a87b6300ccffdea8432babc0bb898b1eff21ebbb72de.png" EXPECTED_HASH="24308d48eb498b593e55a87b6300ccffdea8432babc0bb898b1eff21ebbb72de" -echo "=== BUD-04 Mirror Endpoint Test ===" +echo "=== BUD-04 Mirror Endpoint Test with Authentication ===" echo "Target URL: $TEST_URL" echo "Expected Hash: $EXPECTED_HASH" echo "" +# Get a fresh challenge from the server +echo "=== Getting Authentication Challenge ===" +challenge=$(curl -s "http://localhost:9001/auth" | jq -r '.challenge') +if [ "$challenge" = "null" ] || [ -z "$challenge" ]; then + echo "❌ Failed to get challenge from server" + exit 1 +fi +echo "Challenge: $challenge" + +# Create NIP-42 auth event (kind 22242) with challenge using hex private key +TEST_USER_PRIVKEY="0000000000000000000000000000000000000000000000000000000000000001" +expiration=$(date -d "+3600 seconds" +%s) +event=$(nak event -k 22242 --tag "relay=ginxsom" --tag "challenge=$challenge" --tag "expiration=$expiration" --sec "$TEST_USER_PRIVKEY") +auth_header="Nostr $(echo "$event" | base64 -w 0)" +echo "Created NIP-42 auth event" +echo "" + # Create JSON request body JSON_BODY=$(cat <

gf+$Aob-%5^y|k7Xgfu{UMacC{xi<6g6!0!JdYtIq|$|0S&U@EKOseRq&5rFn@}B zak45dNd-%8$bUJ_C`ZW1*R`4WR{cS{Ar>M?yv9X$oA#G^k1 zV0T#>X{7OV1PbEM)nUnk^E~3V$Efio(+>pYQls+4`v7xgS@rhx^`7_jzFB1ih3AWH z{bIt{+6$wQCT0lq$h@ywb91iGbNbFvB9DD-TU>Vz*jfB29R`xio>K_^hHXpPH|z8} z+8sBJ)fw(=53=a50|sJ+ z7Q2rx&_YE@-721Vq0sP2smBAUv;pOZ1HdkG7sx+2v`&tdtkTAAcjwpJJFh3oOv@o_s;wp8z!QV%Jhl;e;B#C?lDj6Ey|qrPV` zfHvo9+-Sq1;BKngB%?9@Tlfpwx+2roKD70fb<76;ozH#K?OYwSGo5zmuMf~9>#2d= zpakohUz@1VR&Av5*#e2>#nNUNrrG?DA>wj=b0bPJQ0u)jt$)p-#eh?_J~6xXCs&4C z{s@$~?5YvxTf36hh^Bnd1@v24Q)Qn4FR0-_Y5vs$I+|=O9k3H~NAh}3EwDwx7O%UM zg@GEiBioxIS^_>OSZ&Ul;21_)g=?6J-WNeQJic*m4nW##zom3Qo-y~@sz9bgTN)1? zKrO)S)@rdEguns`7l?Y!jTJMlXjVf@H(QHMAEPR;&MlksLCEhm0l(XuI6B<#4p)hW zhZqo^NaGdA**$+&T6|KJLriU@DD$BVjUJyyIU7g`r&+RR?k#qw-H1loYO0Cbp`q%X z-5Fz`UtsL;7q5~A{K))aM(S-)p?lgX6 z4*o#6&IB99AxDR%Bxo1su?1)!>b+dg@pcSknOH(K+66wcBkp+4hFGdzjU|!Qfe%=nhv$URW zQOTL4?lYZLYWx6hpa*LGsMp|=O@b!$eW6jKyl>}G-q$^#j!}Uh49qb4+4J*sWvqjg96k z+9HkTVygzSLgZMCOwj=My%>SW0njiI3Pr0;F!j_HkvGAGeO9D$`o`6#G0YnO*aMB2 zzVZ1pNi_8YVK97lr$#Vx)vXVL**GsK$8`eza^hGXcWHpTze&g@5qtEYaa&N=CZff| zbcA9%#0UOEmGff@E%zYG*XyN{odZdY2rZ@>|^jxdO2W7Dnv2{>^y%K&IKl>Kj( zsv(Z&RB+_yR5}vz3~(nN8g@pcv9Om&Z*{vKYaP;t>iA+CU>qrW6ZWozu0SHyw8G$)VDn@Bp=gn$1zI#njI z8o{c_od`|-k(;yq)gz9`{+>ZTgM4`@*GavxRTBh$b~?bo21>H**P>g^xj0P`@;Fnc z$Vvn`0mwPnyWp+G6l98gl@PW0krsI4=UsyU((ffK5-);q$I-Yg#h2mY_^ejR&Ku^j zqxk6=?HfA~Mpp(E;jy6I3xC@*t7i%ttbN`2k_Y~GG1g(Z3<6@Y(UoBf8ByQO1!2ez zhvGJ4XBWz|*y+DTcD@T_Ji%Q{!^X}u;y|9AjiSY~lRPcWPO-7Gt=LHxj9Vq?a@5tE z-XoToKHny2Wx2bI>n*VGA{E^RS`S&+Qjd(HJd1^k9^O)q9GqrhHjNnzA7kV_3uPG= zULBccq0m?e^-D%^+{e^NmR%$BWn<}eLBm3!TWW{oXx`_hR~DKx)OxaDrEuu;r1E2K1``AHBOrxljX-?m`#M+NEltC))h4MC zX&QbXRDMd8{UR;EF`5$ZLq!^Q++ab0ccdBSpd9J(&Bxvh#kIkp_`V>;HXjgWw1t(|UvoM?RR6`h zMrU6hyT~Sq(EXeXFfLLFGsDQf-v6Ak7yjoENF{#c#dbl44%yq!j6(TiKWVHd%XUuF za!EkTq=1&=kw@Bb{9Q=Il@qU+e9`$AoF5WV29$`veeCPq$JC48seSLA$Gu}?i`Ajb zEoo7C`Is-W;N(uC8seo+gdv<-C9fzzMGwT354;JmBsrSl3n4L)DktDnZv$$%- zD1Ee_Wa#fE9LmaG1P*dfmV1wDFXhl-gWTWAF`vu4(WV|&peEFvylOaSG??8oR){>1 zuZec8sPTflc0?#hi?93y(_V-t%l%509rT(&6;<_O)y}s8F{Gl2Lt|8)Fcbu^a~b$EzZmDNPq(ep_n5n8x4Gx2S3rTkW-J5>v{<= z6C(e$2l>S7j$e*0e8m*6*dTe&844?3W~_9i9{d^%OHW8+a4>L^$FPeZ>uoOv)IXJh z(!ktbP-dx9OzL##nKa@%^f`lUce4RehZeGGnrCjc^u`a>{Qcj86dpC@Q`-<8io51$Yy5Z*J-TDr~2uST9D{FaF?-dL= zpPQa+b>Ya8^hE}*Ivw`J)*hhl&2Q6M;<)TH^X-D`W{>rk>f7HN+{VGe z9*gmfOHxNz`xYTNS8dQ;H45^(%bQ4+Em|xZM8J*#0lPzhlx;HF z`11Mmp&yjL^@B2OegdUv*whju&j@jk?o8Vk(RSrL&9*8jtd4&b2#GfLr%f}x?p|xm zWOVs)A~qc@YnSveyg{_AG)>=RgKKx!!-D90_x(Rew>TBn5QU#m2#KewMN@~{0-n!y z-+?l9&-cE7jg-9Ms+L+};^-zdk0d?;>|1+7^(j5Yr##ha&kNIO%V$C-?a~ zGvt2x2a(GixmSU<>rh!;FhAXtx}d>YOLsvx(`|LZa&L?dPq!Dh_OMrgSQpf9u?ucs zRQfK6!hFyLyJcDtPY;-=(p~UYbI4Plpp64)Lk49e{n?UtAndSjlD89oeIJyFuPK`{ za{Uez8M$s^?cr%y1j2xZd#gLib(ahclIw|S8Xg8ta(xm2(Xfn8YRP-*acDrI=K=>Q zw~!1zURIyOf5CqC=y@e1vbK`_Y^?KbS4dw^Ese4-;BWEr##FPB=Bp&z--jTUP1h z)yau31C}?X5U^JSR`))fSLNg+Cq8He5elAco0mS1`OEtg;n@H#K-i@>)>}J96UPu6 zIN>@x%zX{v zOJKocHuWa!hJ~E$UJ>oLG&*?6BuM2Ney6Niky%}b>=la)8C&;R+p;vhR_Ci|zm3u4 zRk@WNL@;aO3nb?ohjT?}@Q0CxM@-*#I}b6}%*!oNV>#X0b`AQ!@+R6@oLhtJ47v{J z$gJudn)!rRqN4pi$6dOfTawkeL&i?txxt+~Wcw+5bLgn%ylxG(xhsz9SY><;*^@g$ zBmPwrdF0*54Sm#P{5_G=H(_D)xQjcI!B!b>PS)p?axJ<8-)!>Rm8ALx zsmKgIgoQ$kWKY?|(XcV^`seGXZZ)>*^i{O}_h<88E&e3(J&Sxnw$hwGhtP z78d8P+PY89Pm!D+OOwy#U?}a$$=R$Qm{owusgBIrI6U_s@eMT%?YYtUUvAZ~=IDlh z)K}g#Ew{4X<<@=H1&3{z@8zrypWT=UgQLP7!DY+KKFuo9!#)!_*j9{4S6+mrh&3((^ch0(#w zV!PH4*|B8uTKy~x?M?VM+K>V+n$8d3-?NwWXrQh(u`d_&c#j-|oXitD5C zOSrMWcWu}6n7G3ib#tOe9$Pr&K@L)l;f57YsD^abhthe-=qOQ{QTQj@^y&yfZE`+Kzh zxeR}gwl;BLAuFY;C6N;|HnE1a4t%cVI^*CEV~4pfj#86cA!;kIaT(<%bK~+D*PDlQ zjm(PWVPk(t9QxH%N36eD#)vUI;1t{HcAFEB{L zRvsRq7(_b4; znM0x_hxQ&?7oa+vAEnzB(&8xXwRtH*3gP)+u2p(GnL((R>WaKhM?C{P_R%MIAfCJLjM%%CX6PzkmNC*~g$7l&Y_*r&uV&E% z?e6H1i~lZzEQOmXV>C=pT{H9Q65K6A5QP8YO-Ip3+=9``R#dpBZ%50G_sc~TTef;B z+@1g>{+zZy`c|x68|JC+hA&3bs~2*4DgET7`C*j}Kb1so`yNKBn?8v&64A`DtQqr+ zx1zrggyT?T=B1{LK5>xe6*#X-9NU-AZ5}&7(Ubwy${Am!ha#0MR#GP@N;D2H=h|L# z{=#P%_UCyQBBd6j799X1uWX5<(!MswR99=>7!pP0T2+2m8%K5i8dA{PA z1+4`3<`P1HS5qM!XIXosX5+<`4L|Q3x$zLNK$`+Y@-DmC*V!XYAAkD zkuy(onPMbAYLTPG>_n8V@}&#NZp|5g&ZUzO(1dQOvrH9%hbV)8EriEZb+qQhCe~fx zk+)QI%Lz0cyPbyrT?pUH;4iPc^wKPNuj#;hW(cC|At|_uG(LiKekqz3XqYOC)gIk>J+H}k zwbafl@d&%P4;1f|fKDXkuJpH1{KS(qItl{PVcNnuZ~zXwMWxWZT=K{oJtK_d!xQcO0a8 z!O<}|wU-s=rsSY(#y@!t*Z(JG3PPlffVJdYn;2PY;vhuV#3%IlEPye;ESYB;lH zfWL+XGV>Ju!edb=!k6|QsdzgAO!nDu7ZD5{8EMp#AK^6qB(+TNZah7+L|YA_ z{%9j>c(G3J?=;Z-la_9UJ)#M+I zZ1Xa<^m^j>*iZY0Gyj~*hrT#6kNz(o)#tC*sWBqaU$;max}N)3#Hy4jx$|;D(1D zet+}Je=&(ola6Dl16Nlc0x|!%*89LQ&i3j13LQm%N#^FX%}?uVG`^vu=x*`?7yloc z0%-owQYy&oD$VL=0pIOd>aH(?L;-L|vm3L;kkXE#bE%xV47GL5(h!-fkEClj8kwL1 zgTBrl?q43eOs)*Cs$ZZ3dPyJ?LyI*L}_4c0Pf^Qs%rt zRq;z0`paWSMia;7drf5&>=BLc9E~5(Ir}s6<@5NtDqP~_7|I0&J6&bI=5O?qnl)6p!&kX2 zs1iN0s4qWbL#SzfL($FxI5KY+4|ib*7cUwuxE(^cysvKtE}l13aEZKcd};_OAe8gI z&oMlSyoElsgNI)1Q`-qTO91KjA^bZY`WS?~SeXBihXJsb@dfJ_<+AQbOqKnQMcnK3 zSUP_f^38>-=_q^qpRxLOI44 zv@Ozj4AbfLp;m^Qy|QQ?)1N_En>Y|B+?Hy;R||8FR_6 zkzoiwCX%;Mr0}_v*{p9g_GJ9}%ibi9&#M{ydZghWR0*NCyN12QTY(hRXKHfrzb6gm zdBWXv%$MOLh@u`KWA;8R3xn_3XkmVDn z)1Yo7R~=JbTr+*+8u*XQ=uSxuLC|Qty14#`Yq@8`Oya|q?0k+ZbZ@d@$Ry1}?TaEe z?n<4?`s?y?rt%OXzoUGCbKpt|P6x~h|7r=-9O=o9cSRxpm+(IyoxtgNABx%WgUkt< z^Kl-SxsR;Qk2E|VOQ-d%?dL$@)j`UU4+zW5 zp2rM`=2jPWjO?501`TQbQ13I6B27nvImP}}FdwM-?7>l^amnB?$9h91z>!Kg^Fuo6 z*@JSG(|R_B@0rTcweYytv+-@dUrf)~iST+hF4DM$-<|ZVZGxYlwybJ;@hMOBoT9JLvQUd64ZABhtr2zQn`uK`BK0+*V&-gn7?DbJuYi$sYF~9D z!@)+vpUE6DF4FXdWwNfS!Q;sv9v@$r>4g&Gi^XH=MxfnzI=GfWh;p>OJ7j94>GuCp zw<2BFUK9_it@Luy{3+%5nEfP6^Oovo z`H=4fGZ68FtPsi|_Hg=jD^15fn1Rp6o6~>%92fk|xR>TMeV|euYhz0n{k@_fx6vlf zt@Q_^R~dgJ8jqFco80AM4zL(XN74HY@=Ez?s}6cC;^-Xpu`-$=vMr6~!%7{oCivY{ zWa`No^KOwF1z&%3d!*?r{g{^fLPW1s=yA1|BTZbVfo8@q1`RU!-ZT0u*N`pILRS0I zmwJly$*=R|%;KEMf$R$Vs9GD&s*3S?bX9*>K@h{+fr#Ts(Cs!{?p-|Z1pWjw%%H5u zjKT#BqG-mcohcGVLxOMRNPN?YV`XO`$*t1T(^_Z$5TAAF#F^3LP+ZdG8uz@YSH;fJ zT8_Fxi(j?~U1Gvht6Ln*d1het@rZo9{=+@I2nOBVQJv4ik; zBRc6J2b{LBjzPz6x>fQx?_ap$mGQTkQ96o#JzYIP zuQ3-)lKS1Cj6_kw@)@9}1(GA$P@=yI=+Vg>NV2t#x^`f&%=ryQg-tNVgoPRKyE<*? z@|fMC5weL3>Ce`VqCIFs@1wfwnNPBW8}P67nK=A^;cfX6Ufb4beSnEhv!?!p--AZ?`lTa`NZd;qAJ)AXZ^q@oCa_3pK$80_V*3x z->1{!3W?CCC*XBW%m)Gc(*d4nd`h^Env7~Fx@msB^l{)TsqZ*-mTqs3q@7w#R+j(1 zkUaVCT7BDteT@&3gF?Ps!QYOeuG8o>Sr_>@ooBNFa-P14T(M20XFh*U!I>IS$d562 zRHIdJ-hk=R;v8Cy^gNpgbcN=0tXPHj=3ABGQQ&kkM zhuV^oy-0h$4*7AVLGlc9vgE;mEx#s>S0E7IMjA40PXc>ExD|+Sg)FO|5KY=%=j(RF zziuHJNaFjcNWyP=pYs2MAo#SJLK6dDVLYbFzY6+ojQ@C;-k0?DYI)nc+4??3N;8q2 zQFcPVjt%C>fVMED&pSqmFCo)vlV0mFUikV%s8L-sb5ac$Scd9oNxR~+Gj)u&zJI} z;Z)(eUzce7I4}ML8Xxx~yYqObTCLT{00-(msxa#e3&UgZ4_LFrufhn;dLr;~lwu-? zPm&QaIW82~@1yZip%poqL8FD+me%`zPo78e&^8FZM9hyTQX^i^j1FEGX>6jR8Ir_d zXaFr8T&uB5dADdB&Eks>)lu;V8yvhKz;&<+`$b#3tIcTZv8JZ(eoh;Ew&;3j%^Ke) z<#Q~B`HE8~)AB)!#R#NU*fu*ozO8bWJfsi*YyH%^GwnBRSw92Y@Zr^FC1bmI!%$J! zT4Bpuzsj@~rj#u6{*mZOe?AvAeukz7zTzJR!pPu2y1^_05mNzPmpLt_2f^BI>X-8V zvhsybf)D5!z6E;JtN#~zp2V5>PxP#19sA$tdFiUn<)hi?|3c5s|An4$TcYQb_>X~j z4h+cIv*E{Fo3+(#4IglSsG=}FqBx7H@O`RZ^cPR`KDBv|&A??rLffY$ks1F%zb&Ru zGo_hgf1Zvr3`QUeovPCOj-nZS7FQ$XFqTGSXub5;=WG^!CDJp>bQ>Ljo@vA|1CuJD zV#R=o+qYK8m7a#i6^G+oi>rF8d(e`Jy#Sz6y#e@nK+Ev>h`vD;?@wjx=h)4;y*Krr z_2EPMCe3F!Uj@g4& z0t=Q$jQpZM>VMH6Yyb8C)*tVnBmay3IA+cN&>v0U%XHS}<0ozZVw>BWlFmFxiT+pi z_(>dlB(p_(ycR`D(-rD_mOXxH(&qG3{2%Gr`M=OJZcFrp@9JscDZf^3uC_4h`5php z&A~egdjD6o=d}%NCQvYp_k7!WGM;L95m*DTaQNUaV+Z)bx^Ugl$bMCSL7Pj&AKhWFbe@*W< zLnCL1-rLxc{6El}^Z!UMy9n9xJ)KCxFXemmz?E&iq%VpxntW>qx5fGq_$=&3RDko# z#CAj~ryy6xKJc+fj(-uL2C(kEv~2gC3`()a+_!u`-~)(_jcOYFKD-eem@z|F4`gE!tIjkgPO`p=Pl zIRJY}ZSt~Q)-?pq>9xeJ>Nv?}Kl3`w-TO%0yc=_qy7@?=H+63TXu1Go-MeX_?I)qN zmHPSGUGl!Cj*T+*T_#gsBD~-e6Janxrng`fFKCaO7W(+tV;*G}Fao}-TZL$)miMcm*;k!^Z z@vk|hIW#isYL12T<)0N5^&NZFEpHi`yBBbL7NW8XuwB|XtAA|nkb`1QX}vs{51#Oq zv527qSG^lHmQ2}+%PsvL#U^BZP*YKM-pTh1xXLpZ&MdDNk}s_C3VzBuiH;B0b>qkS z*fMufk&W%9=E}&Oi|ptK{TZFpvruo`|W; z?ws1h6(xb@jO0{7Y=q9SoXt6BHsY91-AI12c{n9{T=LXDIW>ddnUY_d>xh9)-bY_7 z#Bbt=GWVBzif8tbS;3`^cs*T;pO85RE&oaKKARY`CXauIN)u5yJ>PNC3M~9$8@e+p ziyvoA2b`xrtup?meRM&=(6@CoSrhI0p*we}+b){8#?E8pxlh#@G`V(pX7-64QaSzn z(qbLV;j1YD2lxF0KwGA*h9O1g{O4y@Vx+$~JrhS+pj7KlW4xpl26S=+w;(_aI zua?JlQh6^Q$&MOGqr@V(jJ2R%<)~=<>-2!0{zcTB1vc7h*{mL556kf+4sT3kZDur< z4g$*W8$@O?8C=a=*3ykLeBJaxw^hYGHj>w2|8APUCcnqV_%T1#4F0N)`#1BNA%b=j z#|=tMq{@PB@g3Rf!S2%@N%;5iLwi*nw@x z!o=v}L`Cl^wr+Q*Vh^aeitUu%@mBsV;TBja`F&A_{%x#GrPs$MO1sBDinqnzsEWT* z9bZ-*|DZDdO}r((B>pNpoAY1YX8w|`D);>V*n1cFIE#A!e@hE3rMfMmqM|O?N~^sE zT4*bkgeL6<(l(|k77@cH*(BR;cGtZ$NsmY&t#(h%5j}Vg$IB6KM~)XnkC%d2ngVSB z#R7^5{A|SwPbmn3Lb=%d-=FV1&+aBofpgC9|N6gP|Cfc#%rno-JTu?<&i6LonSG*a zZ}HB-o#PcfckihW)>b_>X77@*H+bhUgFk;w?U?;_#fPh2C_Yzr z>Mv@>3=Zy|SoN#gA{FjE_37HGN5;G`W-opm+5{-{7k9c{L8f8M-r{&3P8#!6aoIQ$ zC)bX7ptx)TsZ(lsw`>xrNG&i~Hks5^B~BtSeT-LJb~1^XDt?OHT}acVy~@Igy0?&= zK*y+Qxz)QN3^5sj4<6PqyHi*A&=?)KdFoy!a1@kyfPz&!_v8LkZk^k(bq%+_Hsbpfr{&D z#>m13c_j8f=%b(Zb>MC^WptAq&z`9F0li_OofKaA!Db3hgRU^YyP0 zl@lKyxRA3-)ySpiA=RFbF1Qyl7DDpaxWV5-dnO#ehwSkdl5_I$dy30mYFN0daJQ4| zByX|fcAVM0!&AnSUV+N9l0AVEKa)>feewRlEaq!c&%)AezpG*3oGcEKYd;VFyb7e2 znz@f36mx!&?f{T>-EKWb$BDc*PlT&2e6jFL4$*LL3G}>TU`~HG*HiJ5zrie@5kK*o zj~C5l!k^E+?9jlL+)ks+T!x^X(b5@eNGg2|)mK-*%m>M?I(AS3NR2gbiwG<+k3!1ST|ZQuNc z`&VBC#Wz~`N-N$<#iw=zY45WrF%st_c->HN)5Op@kjvzS|Z{V#}tTAn5 zZ{!6a-C4u0%bhp`4bbn5QqreGE zw@e!Eo+s7x|MpxzuVMU{nj0ApJ-C1UWcU8KVegL#-rv1`LjV4A^|7%c;L(Be#zI<{ z$-%Kpw>0oueGBgof4l`~HjLT5YdnN-l>NQHr_#9-JC9GVe>_V{!r~%O+ZOk}8v6yO zI(~_BEPZn~0o80jm-NAgfIi^WvY4NW2YlflK8)sg%#tmqjMFe)yYcd|i}MqfY#BGn z4hs*QCvGaGa$G%JRl7|6_7e!jo#E}_+JTF1fz8%ze35fSU9>h(^Tl&Wc3X09)f4dU zbBxapPA#HAxbElG;m_P&8{WQbOx?hmTi8iJitj*dY*VNYS5%41V}{+<%~^>WKr}dF|F+|AA#ZUtfBr z{IUa&S?!H{HPnBwW|)6Z4H@dwrlhrrKEzpcS@ri+P!Z&*{)5Zx7$D`B4cjk%;zkxL zx8K<(s&V1A$6kAcp4R->o{zQGd3KNgQR<)QfiE8FM;ZGc@2wC7xyh3S&SJ(p11Es1 zcTV7XA1(0u;!CCk2>znBMr9>IRM!{(aMP*eB2gmma$cqS7}=9BDm6@fpLaLNFQRqP zh%#fn@4GjJv`EUpYjm;#UnQT`on39!P~PzydF}yuz;wY+PO<}Yg(U#2*LDU`AYJum zu@;uAAEIr%e-I@3c6jqfV4)uX+DJFe=WgiWx4o;6)0F!AJ;Coc>h}q*p^%)4CRD&> zll^wJK!YMA@+RnP`mPe&I{$e5+RfK@{+OSs^Htm{|V=4jt=v#H*zT=wKC;Ysa|a-CMd25B}-oEzzOs zEt!{!cMtw)G6|i%DCidnwq9*eEzj#ZL|8$;Vj*zWvo`vsNm8Ai!hY@PF#xGeq|`se9%AJq6~C>|c1(Ug7xGWnFk@U21*p6n5{_!JJ*S z<0Bh!prAW1ELgvc@Z6qv-6U?u&kc<~H9BKw@qYXyFide5*^V*0#!U$=d@B2l`d3XM z|EGZ(a4o62x9snh=dzRRm~BEnQ+9P>NB`jkpx5go903}HUKR)Y58;}=lOorR3vYfi zUF0awX3_m^{MbNjMEhV_e%gvAg4h5~ z4<0yq%lR*DjlKlG*mC~gNNibqSmF7IgZm5jN4YkG!O;OJPWq*dU@D?B{x+~Y<9<=2 zVm6GwGz=`8fDJZq1E~!kQvLe={f8Q6>>il3p#Qgr`k$W=zVb9Mnl!-N-M<58l@<~N&KR^?EgmSKa=LVnHjJO?_BOC!OAZi&E@sQ-^)<{2{u@8 zk77bTI5l%{@>QG;RrW7k_WN#Z(2*xqQCZ%wl7g zzyP3YLHPwwR6Bmz7}To{e@r(ZM2nAjIuDIA=sE0Pu+a~djQGTg6#2MA`*iCgQ|I#B zV7Xi#sH2W7xkKckW)ilet{3l-&-JC@8O>8Oy54^yJqyqHs$KK%=-tM-XNgQ~JmZ5V zJuci+cw+g$W!1VaYw*RxunuSR{8xHMn@~mh-VgLr;4z*t5fMVk2}Mk&Ob<`VpicF& zkzYO68~WQ}<`XUrgL%#yJfx|3&bx?qZ@HA7crB#de1|v9cVD2l=7YSO7U?wMOd!FB zCT~|2lKv>B;O3EbBau6$JSpWp8J^NrO&=P)ReVj8c@PX|GMINwOKIN2o6$D(;|-cU zUpT-9VgN^n_8H^d_!}M8FAAW@Z>y$c!;Giu#~g;9lTVL~WhYx&^#}YqYF#&fa=Tlg}yY>yXZlf&G`WbiEj(Lg&EGl*7462Z>y0>^g z7LA7DxT=LaS+^%z!!@pY&Fls>HSlcijApWR_9omG^x8&P@_#e0k;Fhps*>5yqES{(RP3TLNijL3Gtg^P&VCy={zc>pS(MuM1?IHLQ@!!ym)xI`rd> znuLL#jlQhIi8CkX)M73*Ip=dKQNQrv+#iOCI+xyPHAjj%KNE&liaKdTol65zXCF-k z0!7-Z0+XTxSS!di-Voir$*f^KqYgX%I4ZyJ<)3SDYVzfu146V*k$<9Fr^rmk8bm6( ze@o^tcBuu(O-MdiaPH@6F3IOzaym&AY~@BszUY!ClYGe~eSzuch3g#3Yg$VvU-$s% zuIaB3m`d`}^1H^3hj8N=GOE*~_z(hvQql zju)&3AHP*WAFmbaAIN@ztptc}3z|Mi2Ez>pcm- z*EqcK;P-e+BUEt>RPj=%Vpk)S@lvRB#)}`)heeqMwqMCFh4_}gLYa5)MelY}LC0R_ zI_98LBF-h)IGAi#HNNX0L&67*-$jAPx+rHW5*$_!`Z?F-aA_cP+iI z1DBubed(F9e1OY8KPfVVK0g7c1Oe+aJ9TzzdI+VQYm}?*ICW=3(OX{p!}8)^wlq%- zbCGbE`_v9|OK5oW_vxe0C$ahI+CJ&2CN?}-gMkYQImWw^Q|>jzov_@^f5A=P}{Uv&dtXArXXVfc0Z` zNm63AS$_=+p9z2LA!={baIB{{>QfjgCdpC(2*aCJK@r6{FnH&uPOl$0XOaLM@lMdn z;h1kEIu>{zm}!Kw8amJUv4K8@cF;uq;uM+J^RI`<=cgRv$MgnHnovLEuKq_3^}m1^ za4FSDqrBV=cUZo49Cw(FJDZC!p0ry(%lIb2cEZz#xd4V5#$7Pb_*VrLE|P3Z$7_pw zD`>vcBZ`k|8*GAi*Pjn_2QsOHJ4LUy7DB};`t=gsddW0kCntt~lz;u<4l)lPxDB%J zZDN=udhg`_{>P8RdajJP6hfb0BWE6r+f4s$%ZuFC=Xv)!5&pfpCaVk9%}rWNx{vEm zdS{xnK5T8gS!=_W(}&~#{oY?#@}>S%$~$`)e+9yCn!1e)_}bQ+jdq(jeihgNyE3VV z(~Ds89ns=lb_Z4+SJ7X+Ls$I&`{0B<;mvV-jSllc5;f26J~zC%$!h&7>PYxmR-K6xCF32*9DbAyi_ z7ahyxgBUDCIz#ni*d_Yl)QCDire@$n2+}OB284*YEFyP9K5#r)x2SUW%hr>o7LP+q z^}3OQwPLwxPU1r7UcNc2wN79P~W^pVK$Qcu( z#jB^DOa=x6nLT&vMue*eC)^p{e1fDADYwUvz~1?G{yHa1ZN0R9+Zg{J-8%NOKd|?+ z8wS2=QbqlZ!CPvqpL}DC&Kj59XT-=Bgc<7^itZ=b6z`xAYsOSTv51D^Ck#-E1)g2L zWU2!2Eao9R&H23{Tm?iy`?KUZmqdla6I^+_c|Mq>mTdW_r4jmkCpoo+#zA!+j;u; zS5Fo$`g(r7NEt?<=e!2LuV?E}5hTtmMdPQv+kY?e+?|Ru7!> zf54g0;ETuXs$)ZM+g zL&@9Auc%#^pE~aHYr;QdRIV6bobl`8{n{-F-FLY7>r!8Wer)Oms(ih8(p2v(cFdgg zH!yz@o$4qyA4?xk;^MuJ91CNacfa?J$B}Cm;<^z25Q@DvM2}-7u9|=;$!#C;DW8`%%R`nh~aF{#G=70H2qb+*D zo88(CIqeClShvd9*~V{J=N9vGsQCF0EdKD{C^Ins4Ig12cvAhsC-ZIfThFfV-;IG_ zpZ5v6=N26Hjc2QdYUE<@D>7YEg4Gf)esy)|x zvvX{Ff`4S#fhr-o9~giC-N5mj*TGH44FkPM$GfB!FXMn}pnZN*MYeUP;@#;~qD}7M zIA<0bYi<#8N&0;kb?u|_$$WHeHGAvz#b4KK3sHcdcJ?0otDy}*^Y;Dm@X(qMF!B0{ z^oqHc9R_Q>BLH4KQ+Duc{yMSGct50~>k#+wmOA75pe~tp+~DEUtIq}UsqkkN-8yC7Fw);`2D_dQXsruY|~%#j>|^G!9(BpxB?p1Wn!)Qu{I#*WOB zoSM-u$jD5LbI0Ipe9YjI*6H|AP z&rh-d(+l3m_qc0l%IkB+|a8z8q*0bQ=N#Uo`@C?9H7<55X})`I6>`@f!c$PG z)0YFOzR5~NNNCP3pG#t{64fNCm1rc-`>@`sop&!#6yu2RK|RjExC)?t?! zH;-Q+o! zKPfZaG;Drt-ZMW%%pN%Q@|l`$Y&q0`R#V|7Bs8qvIHo;xoj%WtKY3qMD{iaq1tekj zZCI4|xIN+f@hFxb?>9Wbt#x-Jq~H6wy_5(RRm(eg#0L)3L3q>EKobKK_fA;CL_E6` z*EyXOEEkX@Nyx}G!8>s`-`z?IP;osM_w(|~0Yb0;>sED(>#BBn*YF+ZyvMRVLF07! z8-slDq-X9b<#$;({htAPsGQ$OzEnyFu$|=>XWI*85%=c0@|(9>Q%`!o1kd=YfNlfYpTqxkf-g{*7{@K36%74W?=q99C!2%7-X16oHXW#zfR7tEay4zi_naN zCmWv=DZ7eC=E^&n9_zFLBuPkLCQ0W=StqCBEF+SZ)klW?@FQkFJg|f{S<-jBi@_Yb zOvpR+cfLMuta28jlM^%~1-tWYCwsGgrjfaO05xj^^H0_gK6ayyt0p`k{DqO}{3d`t(kiRedX?suAqTXX*IRJWSOvmyp@c!C=o?_3>c}4&E z&-Ogz_4&`gvikeH=l$oO=ouT!1n*z{oPAc~N$=zS^H{6FeRMqAqPyMugeC0^#uX&p zu=1CwUxUAqpv4iHv<>h|L!d{!Zv@$SyB_j8mi?sn&&sybI^oTqBM%eHY0y*Dfs@7! zoUdCP@w9v_w(*5yd*0*Qj;izA%a0)Mzy5`Lz5b`$V0=0x;UDqvuWj5!qYRL2`^k)e zXd8l61Gp#Z2L?7$LO;AvO}X3IeL0YyEq~pC#=RR~IDY-XelK>;vFm?tZS3P46>V&L zL~QXNh2{(IG%9&72VwU1TrOI0y365;wmA>^&Qn z6}ZJ?9c2FryAs#?5EYU4xwD8XtU0te<>10VALHbX&O1tS@b0kX`uMho>BE7s1lw5e zLkHNP8`}08rF8HpI5G6~svVA(+q!bU(Dieb)ClZ+S%rLl#pBbN^I?3-%vlgwF!VXG z5!7XVxPA+(sQQO|WnlgtECy+PBFVE{)*ruYSz1PKaXEEOe$MUQ@h)feJN=wp-g7^& zhJNg7X&L=NIksM}CsRx9KIMp_o1Th}6y6k}JBDg+v;<4!wU$8YIF^K4OJWL+2WUOm zFgz08>jw1&cPry~N{jM)+kfId%VPfu-!pQoCH4k*qz!SGx0QS-iKEnqjg1+T$d$`K z=l!vqe{qnnnY+83za+>P!u+tD|8DZNntt~^i&?mbF!V@1>1u~n0zoqAz|?QS&ORtq&+|CeO+Zl z88m^P(SHDy_lbvbiW#MOa=dyRZU&0M2W1D`kfF@XVDMu5j}sQ=4QCf(814GzN&h6Du1)n+w3%~ zz1pu=-{IAPPNlv^s^;c>B+a!X|f7$mVB*ss5L41+GNJNylR1{-Xuu`0unQx+n`gha9 zrRCyrhrQnNa#mBhg=HjdN9WHbkj!864lW1NZx?$%F?ZD4^qEKggLxP$xuM@~DXAd7 zBkeD^5tvnBD1Rwd1aE<@VsjJ2aJ%?&@W;E2{V#t7-|=1JW^M(av$K@{UGlZ^5{N6r z!u=1?2mkZz2{Uu}R|ox3MsVJ4D4)_vzg5vTt`@+K`omvoCJu$yN280i$a zJyy<>75XxAPLXNb{LoEnM6$vw_z5r0F+T9o`?j48HXXd%r~lik^t9tD-IY9aaGR+e z>W}aR^r*Se`B(=Ym-qQJ4^xq&d4C~+Ke6}0>oqt>_Wr7ukX8@zjR6Zow648F0HFU6 zyP(3CD)W$i{SBHiS55k=s>P_LIz^s^Egx1h1M??;!M#4_L0O{Em<#I+)B^3@v{{J@{_Hm3DT06=sYg>u=Ooz>~K% zKRE*!d0(c?!3TZ)6`Q}42fVFhyAlU~0{pD}0+r9I{5NdxP!el(N8fXwX!i^zKAVq7pk*s$NzlXF|601selX>H4=# zw5j6v;+%dt=*@$`l=RDBKNZ3N1$drDoXy?cZ|(Jdt31cr(h)SrIXu^V-AI;q5_n*9 zY_gZlaTQr^N&EMYQSCCgB$L%ACip3F%z^oHsi7866Uj5v{>7#F{nNJtG|4Yjnd@Ek zMOHw-_!-0pkaMkxcg$Bv_P-~zO$4_#T)SUuqeR02$VU2U^K-M!=e=JBFb{cm5!G0Z z)&}&Q+Cv9R@N@GALDUAoOV}u|I9~C%uFxJflM~30@_jFgcKc`fOt|&|FNBB>eZ_ zfB^o}a@IX$L2ed(A0|}~(2U7mH@MGStU&>EP=jr|j@Ql(GgXaxD zVi3Z(QQ$dV{hd?p+&t^=quwcGmErj|axvCc!1Hei7sKHB;=K-@zhhYb+<4!#q<1ZH z6Ip`iXRamr-+|{wzTAQb$CzkbcQ1W$NIbwe=ID1GOg)ZCo%XzX)xKUqH*h zqk%(QZoLddZ`t_#n|hAz-x1lu0m$G-+6s?yE6@(d!#46|eoT#D&A?MnKbE({Sev6i zI{#f3NgRJOrb4IT^yZYEKi``V{`jDMmnF4WxO1IeF9!V>g*G>a?z6eEn-*^Ou0ZP$ zn)eY0wEI>^pXnGm{K$vyuW?yklVAM&%Hr7j-u6QO(72u!E`ylKIjrIk*FoIG@l*eI z+E~vA{>83tb>rc=7xQ+BKJa{S#QUF)c)myFDc5usvA#Eqzq0WUZ>?#buy^&U@Md@& zB? z1jS-jYzYQ%`zfw-WH;-k5IA=s(dH+(j$raOi4EZu9{X$SO8RdFKXm*9xsBfoR(rcK zlW?veWh@WmRxaMUjn((0U+lnMcGg^% za1Zw7HGO{@gX7>!iC>C_R=ywkJhJ>NJ)=?_=@rnw_^H`UPX28M&=H&0|3J^9wjOU8 z!-DtT@KaHoV{f5`PfbwT(7^PUR` z`>uCL3y7K6qg_+Z@RWa$-!=}B;VC&i%{};C?>Be8%=`#{2HrhvurGr@zZ*6`68*?Z^sdpnS0hjIb2JfW18FxAxbu_tCxOj5=Pf>LH`Lp{3tQK5 zT6ae*1OImdzQtTrV6>AL@Uqg$wXKUkG_pTFI)50C+4|zsW}|qvI(+r6>IqL3K8X62 ze?NUXhQIT1Uiuxkvyf~Oo_t_#L-E&~+925~Y+2jbP`r1PE|)iOy-%t0$NTHl^}k}7 zsQp)7QMl>T}@ z=JND|CzSZZ(coN5KTGtl|8X3k)xX2oIfwOc4fP>s>L2&@G2bicgFoLaJN!%8b|%Z8 z_L^))C(xe*+@ZuP#(FoOO$ zTx_LpMo@?HZx;H!UfsdQyX(MoCMMvAuRyQgI&y$My?(nW%bRsq4~?FvOUvcaxzp?B z2QlGKZ#n{u^XMj2`zn_FtESgCdw0{*3hegf3%A~00RA84#)9o8z#Jg#jpEESv*gS z^0CfbC>PJJk7rvl*~EI)T$)XHN8E=Z@%8akK2)DtA4?|MBj>MLxgwHC1ur`i$@s+3 z%1l0yPURL&3@zzOC)(nX=~Z*in6W4lnms4imQ7^xxjDIbzL1%n>x!HNX*ZTSJs(MD;;Be`EFWu)<>HGX({d9-tKQcT ziD$Fvtb1%trdxA!rsbyPLaXwzd?B|eGH>qO$a%4L+Wc^VW?w^N=~SLE&1&w;&`Lhu zo1c?O#uBO5Oyp%S&{ffzS!c{YE7BFC(;ew-cP!7>N^Q=ItVri0OVfo^JC)Akvyl`T z9m*(|JA-epyx@pWPRmtfHN;b$`7XMjR7-WuvCc&XBp%kxTIMEx)+(EyMUkbkTz<*2 z`eE;y6Wsz=cP40K&8(H#1fYv0XDwxZQn79W7L`_~H09}RVgm@VG@Wftw717o0!T+P z(UuQ2#`=A%n5CAUu0%ebOyu%FK_x(j zL|aVw*U~|khUfAG+O;QAouN6Z6Ixc+97?$55UjKSY1}t_bP2t0bzRd%EvuTF>Q^ia zso4RYnwuTUt5#0wsrPR zVPQ-8u{3~|^@()SdLAm}g@P5*V)GDMYD0?T(-8-%(h4e1u3P?YAyt&mWaF7wHXdQ& zWc!%df6!bqbA@cOa(2ndb;VdLBQrrWvOsJ@0*d8XaU%*9GQzlcyDMmzK|#?K;jLdd z1ioArns~{?(2AAKEvuM-*=sYMq1iq0)=X%2hN+p|ndk`3?#|4!%x)#m49!k=@+RM# z56x~(q(XDrkF9;&TtsB5k(&7olXa?9ROL~F)jh)+V>L*0oSliKO1=(741Yg%gS z&Re|;#+)@I_JA(u2LxuihlQi9j|M0-3vM?iBa3U)Kv_JlI2?vesxnMi^G!)JS9sAkv_ zattF9@6Ety+C#=ZT7suw-Q*ITDadm+9@0#=fi?Agm3g(C~ z&}v&ZZ0Y-}!m>y9!e7Y2$_xmB&0(-WgWfpu9-8BDG1R!axinK1MatR%v$R~_CuIf6?rqt4EFX_(M(B_z6kHT6 znEZgiM2(4|Io%24pK~Cr;5g&WB<47_GUw%LmYD4Pxm6~3)OTl+@oxB5TtdV0I80sa z*LYr{{7#3}?Vpn;7e$t&3(0mXHB6S$g78H;U?H&z61k)~g3MQlN0v6NTpkE~?~m3s z)fszT962`>3flFh11B;T+>}KeVYfb%W!OMkZEn@gwE(IW-imoHyNhogoFkeIDtKz_}a2A|HvR=$2dlZeAkYCZQ+$Tv6BFqDWbw50;-lkBIgvNqdb5Eqo0EUmmL8FI<(} zJQ(oB{c@4%PIQ<_jScac5z+jak$|kU#PX#Fv=dWnW*UK#aB#%*xJjyLjTp9r@3FS7_$=h*d^Vk2z5irDfB;Q5h63=k$HkL74YE4PDWJ*OU zj;KEKA8SpLtjG-NL}H>ND9Ua!0w_CX?^J1x zu~J6tV(}pZIh+qzA=91GbhcP)u;tQ=EF>Yz+5#!sXG#nxB=fValyZE;Sngy~A*kR8 z^oI2$Gz@Qsy((i!0I^_1t}Qo1{<`wlX?baC`M%N`QVaTiE)#D{beJ+H^|^$Z@m^_J z?%ze}0#cDb_x?<{)tT># z8@?p}RCv>#@NHnpIIQAdT}e zxQuGD@F`|_-eGp)#FCYKnb-7Ue?FZct%cE&EhOW)7T<~nAJD3^7rq*R_LYOqiAI;m z^2^Z^hp!c+LnCb#4feHgAKJMQ7-YnV86P%buAeo-NNvP=9BEJF%HEjGft?5Euh65A|=}04cWxzNb=UV$dMiE%V>@ksJWMyXu~w^wM*QP>h`6|s%Mtu@m= zn2xI8oU>x(in{j(3fWQ6g+|_A)3gHXkPLgQ#dg6Oo2Xlu0Nv~bSXOQBEAgvbyeEq- zYd9j)ZuxyKpT>eCnpq-mym#aGxkH6wcFvNOO--vCo9k*@YHONn7=in@qP}s~yfa45 zKR6p;nP09Fk4psI*XSdoHsUIl)@-FED$-ua3NuSOU8JlJ`riv^Q8RpJQRGOu$}i^I zQUbwkR*eX!ZzWw6c~=Q7Mla{oD@@5){$@r{gb7zRN*P`i)O5zl5$#9Kc0j$#Ud}X$ zt|V*t4XkReX=+~GNYSo%+d2(ei=xd;{SF&SBD4*uR9*yimdxRSS`jD&PWc+ft(eSG zJroE-Y-@a21AG}9Hj;R@PyGmtiGF8%gs5!C^)mRbQ$(OSX0eW7n9k6L2ZNjW^@4kM?5)_ z>%%VGJ$v?SP0dmkK|`djsc9v|th}faLGAcxb*WhD-zZH33jf;!tn2;~CRw#7sj}8yrz~9~<9v<50$4XcJT{R*oHs~{EHHDq@x_;SdXzS%EXm%{X2!O(^odMfgL^MzoLi>yz?d;H-^WX$<#NscYu zt+blJ;OMJdnAJlaGP(HAp=7L_j;-%(Q41}qN--I$;WfFC440A)-G*v=$$BTm222t6 z9(dcLm6b%Hlt})<6cC3y%Qnow75Q5?>>2qIYD;Uny|467!6toAbal-be> z83;EsiuRW)b%KYIVViETAAF%1_ySox2Dui;Emd%Z-pLkAdq>DrkWjj#11cPHD~efn zloqTG(t$E)z>=9J)RoU?7R{NXaVEP^Y8Ne>JGUxS$*k2pra!lD>r9VJR<2&rJpElW zDs(wFi6LiuGt*XwOih(ih}-`z2_8GuJfMN7&iRq-hH} z?(g0PrRM}@)e23E*^#By_H0aWW`WtiS7k3fy>fT{^#67}Uj?y>OPcCxn(HDL*4Mp1 zQol5^Vr6rrZcY8F=2gsKI;-XC)ObNqH34A;l04MY)=mP8waznQ{`|Ino~7Kx&Ym5rT6o6nsA_5qZ~7kAjxnu(g9!n|EVWijm62+(bay3M z(RbU~PHQM}DK;D{3I4`Pl-{q>X79f(bOpVbx2#}P*UwH zZYW?PY)66-pA2?go&B?9w-;DS3ZdAU4+kb&_B^*|Smso|$dPltSi@I%C0>-Q?3J9> z-)O6}6Aria1+qZNQfMe=0!QViN{x?Vzngb~Z@qKj`O3i9VdzBd&MSvXFmu(B5H_2Zq43X*V zI%f!dS$l*~s&cO$2)%51NgZx?4NI9@!LMB2uayoXWO=+j5sNfy`L3DO zRI-{O^_%BcFJiMSY5pEPWmcOUIct^gUq_YpK1yptzk(L{<=y6e@WM&d!5+T3Ev#u_ z_q_#&BihTn&D%%)90TXvZGZAu2G`GEjV4p^7#CCYs%qlq!IL{M@FXilDeU83$HcEQ| zNf@@wU8~HyP}4>o>^HELIn3fd-K^}v#`hXCaWRltese*0Vl1c}W(_u)k*4FF@vMDf zgk`!6JAaoX=m(suHDZh1wia!L#gbqNWWfh*sCN9&P&0pjIriv(C;9KE`tdwJ9!JdF z;P$$39;kA$b7Z^NJlSTNyX7T@RguWWr*)q;GN<>593$^4mTW)m65na<;?vp_u5Nqp zG-czoc8Nd0Bl*6sJA=gtQjtoxiqox^!H&bx^n{nGUnxFdOT>xAyiI})s$7X8imIav zmp!h!wQe2T^(pE|#dR3r)t&%~U%X_vV9%IhnrV)^JQL4GOK z;+H730s8t_bIo}TxRwl4$c#L=kKpY9W5fBOZW&gg#-{q^HBA>q zE~vXGf<1mEiW5GdxLawXLGD2gl9A@RHO;2s$uTx~CueQ9x2xBsTr(mCA6S7_=;}Cm z$6hw$2Zj}Ic_R|)(wfx`&5AL*w7!B&qLKxNlTpA9-w%Fw4qstwF- zhipXCwZk(rayoXM(`WdEMSKMZu!zY4Mvnr*GF%R6a+qw$P{!j?WRR-4CWH)YH)ygf zUj!>7c%vN(e7?14B2^0)%=4Woj4<^^%v0Vi=wKN*X!ylS$jf~-a+)u1X={~h{|s@X zay|I#hbCS55>7{O#%F81&27_l*xrzR*dM?3R}3(woqZXbFj?Us5qIn*QsNx^%;EKx!50^&M)PNchO(m;@>!sBr?nlkVLPuO7*nMsE|$#2L(Ou{ zYg~OEOQ_{M+`2AkzyRTLqcy9d6?vxOgnmI9Mbqv ze(REu0UDj8fKYvTO2R1$Cs&o{ffx+(<>pM1#%=1lsgO#R?NSlyROJDSdCrFhiA()zgQeE`9+sf)0${ z|9R#1YKECGO55KU6t$(Se)<#YT0B~NrFm3)R>h?MR$!V+2W~fkfq9=9lNUy!x2i9R4`NY!m(mrkmpP%5a-M9BySBN6=Ay0umuP zRGmfD@OwM@c+V>LGr+@OCuCSZkA@d#mHT7$Y>316V0}o9{{6Gd-(P_jK$Fk+%S$)# z{W=0bM{BP%e@AK0cJ|^NB6!(;EhvwxE^xBHnUr-)TdER|*kid1&D5n#? zj~La_+Fe-D9}|QmPz9vk1dP%9Q#BXL)|gG}pqG}?s175M=_a1JMSIkCN(*fCHY)av z46?7f9iNxxShE}{Xu&e&e5XrC84^dK_eQ_xmI3bf>lJY4sQoG<&ZwR_uMTfU!W%7I z_T!OmJ`Uh#%;Ud+P#)2*3!Pa*vlg`CkVbwiCHs^)cg-~2%#uD<9}SP_UuhnLR$m@q zNAzn|;QBU7{ppa}QrG5~j91b&{DB_Xoe1%xWy!4TYo9kE$~&zMoC! z({1Tt{)NHj^-QrOxw8sA)HPfxp`GkKn+SgN_A3~l zh4~x4obU5DAFE#7I9H7jM}6$8P>~$s9f7wa;N`bdT1|dCwnG>keiifkU&BqS`~P*Q zjY7vp8*ha;9hHK91k_%2J0-YTJ4Zn6RkyS1g8Ifr(+e=7K{)JWp*wY1A%y;4#NX0^ zlK(I9w}QI(cvtFHAaK1B{=PprN$zMG)=+4YL>*rtdZqe?|7VuDTm}9Q>x+;7C3gYy zEO=FvqSTCo^vhEP=YRkWcvbzLSMGNi4$fL1ktUOIEXnDpkp%ZAyxMk4cnvz11|Y=5 zJ;~h@VZOfp;WY5_RrR-09l;x2MANYlG-4r*B7Tq!KRJ)8rp0_vbcz6m@jTAB`wG<5u|^51JmJXgL``fx>AJ^#sn zzK8e?{9VOwm2BiM!{227F6FP$PwV;r)?YPkjpJVQkMQ>`{*->$kCoP6`rc-?`hOb-ZDsxkZSJMcqZb2{54yh~tvL9bbxAoL{U1tk@(z+=|F}S1|1KZ?{G|VU z!#SZ4Khr(5ug1OKKj`8^mrt8}2A`RGMpsvFh}(ox?Kozfm&mu_3d%7BO#iXgL=vf( z7%R2H^<13Ls)JsNxH(6W-DjXgA*&j=Q2V#hDB|h|6>e+eIEBUTbytG5O?HAByvm_5 zlCa%>;w%a{k*=aq#8o?l@IA}q#v~lRV;Nd&r*?b0uIwRl@7veX7>}G2B!q(zu8(o) zjAGoYLovJ=TJ^P#j#z?*JD{;N(~*s-FP&^95n7*Ro(oaN?kI>Qa=41e@D=GMY%M5k zOLwK|OES(K1}OeqArwn?BPOjnH1_V0;vX#%Xoggr6*kz>e7@dA(CF`d64 zPP@<`#paeKfJ=w4aTB*e`V{dQJ$9-$sZLy_V=1mOp&O}nmX!j~8fHpYOA)0ZiK>|E zu?UZ{6rF-d!Ul(=49Lb% z$jUP{g?yo#8D!5NX!crWC6Fvx?9s}q7?rr;-(KG`->-4C~GK5jH!*s z9MC6R2VhzmAQRLY=ZgyYSe#K%Dc%;-G9y+`TjTK#0`OdE+$jmW7^fonMYM7WlvX2g zxhNOoB9H*S`&bZi-weP_|%#JLIgOQ zr!ACePER&KIq$b7SwjRIlxa=l zhtZk_Pg~QRZ)nvjB;YUzK&HL47H2E#T_Zq}V7U=xHEL0u)^uLz1g+BwYEAd@$A~$U zZ#{!T06OrQeq_N)g#w$_x579_BU+Kn`A{}tvz|=|^0=^e@mcq|Eayf@ry+>Rgc9o@ z8$>CqoYvZD3*p`GIogjhI;pkPUGr;uc*g(S_w66}Re((?pB zX$GpSPZThV*pomYh!ij`8+;2nwdv-i&}W4n7-C%_zzcK(G|HfvrSz*Ib>|LMp8a2(oG+1>O*4{B|=5&fw@-d-=p!{V{cl+7)!B z6=oP75mb**NwA!N%W_y4DHX|SNf3$n;W(f_fYhlNj3$hC>U;?aSFoVDVYwKuY14e- z1pvlTgOlzm>HMiV^pn%jp}qynW!Cuuq~AK+C0?Qvn(GvHXT-Tkq^Z%xpa3edZjvM7 zd$1N@CA*zMPEIT8P+()jJ4b~;C1#G;UL}$MqASsz!rL8YAYMk4wz}XCbfK-Qz(r}w zX6hAYsh#K)FcVOH*b9lQ)@d7ir5n_?n0s4dJu9Y-yZ@NWq&fzdOLXue8S7D?l*kKV zmUfd63!3Ea4m}t@BI=;~oQ-K!Xq`zW)DpK+DFmEY)%kD||3OGG*kDWz@RedZKu}F0&&6X(3QwV4NzE zQNvl`hiEQ=dIJHsc5E~d8s!yifUqK=R2s*5l`gOys0fTE>PvH1TbpQ*LM?2f4m!l1 zvjU9}Y6PITZ%ALy#(Ma0HnxE#K~fdMUoD>*v*LmpLu`**sSXyt9y(kpmPLCU z)KHdI1|brSn&YY<`WnY$Q14O#nPgwdF2pKnLKcyv6e4+wDrP|tigANq5|7;7OTdqW zkdp|9KSk>lWJGI-xqL@eAVB(3Uf9*n(o8ae#G(&G!XCUCxnl!hTAvak1au$~W4aKY z^`7t02BIJVYm4SNcA;q`#;-!CjYtDy4GI5=dkC~{+985`2(g#1fN21q zTU-@b69O}?XSK=UaRv|q?)JD)F1P_2# z!XBthnruisfs><%Q)y)DIG@xYLAtEpk+%~C= zI4wGc2&o=}G$zhHq139RK;FTWa6Co15)3*_0O^2{btFW}aS=7_bSLEOL>_e#h^7QW6==6A zOOGJX(jyX0g4e4gr=AJH1r@UFyotsgiQZ6065WkJus}DV4p&nuU@Sy10xk`f5J{9F zkP1QwO(3))wp2Qj1#}}qYk?7`p)eg#Gk^*y>yRwd0gh{Or0EdQ4&sBlme!W0A{u4I zi_8#1dk9Va>_C07226hHKy$&K(J2HXR?p4qt?8vfhJa!sScrYOEcZFS-wZz0kN zX~s`zwVAq3DWsi3fzC8{N~>}jvAt6>N}%ejfMha8jIDzYYvLHL*d@>q++o$N3e*cz zISDlcZppG7HHzXXt_-);r{KA&27VEsD-bFdUjy|HS&k2A)psJS@q!gcN!%@I2&AOZ z0?R}p-k3zR)z{65L3DM(WCuYvY(SBrmu_m~0rgCI+{UYLy%4nva|nU%Rt%xyz?%ry z_Yr~`U0_*P+?EOk5)@ZLye1W82smTZGUt(F_i0B^rwH1)&#)EQlvZaQ)`XI9Hp#)qz1ejwA;8{qJ3DrQd zl6Js2;nGRPMsw_5Vs0m9GFSp!plI@^E>Z-yK|!$^BHrBtB~x{>+GYhUw7rL`oE05$n-nJoR}50n`Zd12*`6vczj8Z4znyCsHzG z5~aX6YXf1HM_F_AHYXj0$W@URr6=8stQc;=;9(}#AktF`+q6`oo&B7KK0zj5wu`Tk8mWnEJKpR{r3fYsJym zf(XK$wX(4gN<3aG-E=Ke4kUO?>#X3?z!lKi3UQ%gf5fKLW2dNy;PYBcRXl zrVK%f5z}WB_&lPi;G#m@Iz%W@iV*Boo%JySC=EeQ#2q2r$|7c?=Ue0P7&l$8f+@rb zrd1|SL^T+>kKh ztRw)i&JZ0#0$&liPRfpmZnlm9RH7}w#1wE_k+qy=b?U5=jZ>Fu=u`!~M7DQkmxx}b z1q#f1qJ$uT*c}3_$|ALsRp>NH6oY{TiDV(Au9-@kEc6PwlYIzEIgVXmdI62QVG(Gf ze1?#-vPATZAW5*6+3D7dbVD%t2J}NSBE?WxN{khY&;(FoNvPd1$&B4lQx~ymyHD)HoGI3nWmVZ0zQ3!2@HIen&!0paAqL5_J5-jKr|eZe|#aBH|P17@AdL zzJRX5UIHVFx2qi;f+I{y9UxKxs27t-Tp-9I1rmZkgg}ZDpL`f~RCRSSj7wJ9Md*l9 zM)r{;={khYT7kszEl3JC8!8s?64-cD0q$oNQGe~Omb^h#BTDo_e+dm6yV=Q6IRtw? znnrh}G|P-X5h74BAuWek?1IPv3`vKcrZp6^(h#bVY>HJ%oCDh+Kd3yCK8UOdXuTwC ziNPw&)jFYWnQyu=!zv$t2L<^=2IOP&#s)xH4Gy6XI$;ToSzYwf=E?~`15{u=f;ys)OL`w)U{WOynE~e7bO3*h!Ld9uKC6gq?CQ?7egVbKQA9lSVw9j+mBug8)Kt{lq~i-P;F&4W$h+Lg@|K%E&@fmRt&lZ+Y23A>cEfe>@Q zvmWViidd+h5j)_ne_LVf6RH!;2SXu()hmXWGj8l$?03>UIvn&d14~fv<7OnKUmX$% zh&3Zb;u9H3ZVFj{nJ!b?GhKb`c>$aZl03PIHX9&9iVy`iUv+?6jrfbJ1<8#B8k~5C zEb%hrWfI7N*aq?%keDywh7CXs#tC^PGV>2HcuYTKF(8r@kP)v?$VcD}%1I+{5KvFI zCJ~}8DAX%)G9ox30i%~?IFm*~vm$JGs+V?3+$C~$2^l$R2ceHK`V1>0fm8s*467kP z6>VLS(Aq@Dx4my-VZMJAjAGlMC(f&KmNl+Ef`h6f@rw|CqIcGN-}hCIHVN)32tC3t z5HTSK4A&h