From 67154164f1a3575c795a845417a4ca6178506fa9 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 7 Sep 2025 10:59:43 -0400 Subject: [PATCH] tests --- .admin_keys | 4 + .roo/rules-architect/AGENTS.md | 16 + .roo/rules-ask/AGENTS.md | 16 + .roo/rules-code/AGENTS.md | 18 + .roo/rules-debug/AGENTS.md | 16 + AGENTS.md | 43 + AUTH_API.md | 491 + Makefile | 2 +- README_ADMIN_API.md | 219 + Trash/auth_test_working.sh | 266 + WEB_ADMIN_SPECIFICATION.md | 1088 + admin_specification.md | 387 + build/admin_api.o | Bin 0 -> 30056 bytes build/ginxsom-fcgi | Bin 202208 -> 220584 bytes build/main.o | Bin 123216 -> 116416 bytes config/local-nginx.conf | 48 + db/ginxsom.db | Bin 53248 -> 57344 bytes db/ginxsom.db.backup.1756994126 | Bin 0 -> 53248 bytes logs/access.log | 132 + logs/error.log | 40136 ++++++++++++++++ logs/fcgi-stderr.log | 2 +- logs/nginx.pid | 2 +- scripts/README.md | 260 + scripts/generate_config.sh | 373 + scripts/setup.sh | 342 + scripts/test_admin.sh | 296 + src/admin_api.c | 688 + src/admin_api.h | 26 + src/main.c | 139 +- tests/admin_test.sh | 234 + tests/auth_test.sh | 360 + tests/auth_test_tmp/auth_disabled.txt | 1 + tests/auth_test_tmp/blacklisted_file.txt | 1 + tests/auth_test_tmp/blacklisted_upload.txt | 1 + tests/auth_test_tmp/corrupted_sig.txt | 1 + tests/auth_test_tmp/expired_event.txt | 1 + tests/auth_test_tmp/hash_mismatch.txt | 1 + tests/auth_test_tmp/missing_t_tag.txt | 1 + tests/auth_test_tmp/missing_x_tag.txt | 1 + tests/auth_test_tmp/nonhex_key.txt | 1 + tests/auth_test_tmp/random_upload.txt | 1 + tests/auth_test_tmp/short_key.txt | 1 + tests/auth_test_tmp/whitelisted_upload.txt | 1 + tests/auth_test_tmp/wrong_kind.txt | 1 + tests/init_admin.sh | 33 + tests/simple_auth_test.sh | 49 + tests/simple_comprehensive_test.sh | 62 + tests/tests/auth_test_tmp/auth_disabled.txt | 1 + .../tests/auth_test_tmp/blacklisted_file.txt | 1 + .../auth_test_tmp/blacklisted_upload.txt | 1 + tests/tests/auth_test_tmp/corrupted_sig.txt | 1 + tests/tests/auth_test_tmp/expired_event.txt | 1 + tests/tests/auth_test_tmp/hash_mismatch.txt | 1 + tests/tests/auth_test_tmp/missing_t_tag.txt | 1 + tests/tests/auth_test_tmp/missing_x_tag.txt | 1 + tests/tests/auth_test_tmp/nonhex_key.txt | 1 + tests/tests/auth_test_tmp/random_upload.txt | 1 + tests/tests/auth_test_tmp/short_key.txt | 1 + .../auth_test_tmp/whitelisted_upload.txt | 1 + tests/tests/auth_test_tmp/wrong_kind.txt | 1 + 60 files changed, 45716 insertions(+), 58 deletions(-) create mode 100644 .admin_keys create mode 100644 .roo/rules-architect/AGENTS.md create mode 100644 .roo/rules-ask/AGENTS.md create mode 100644 .roo/rules-code/AGENTS.md create mode 100644 .roo/rules-debug/AGENTS.md create mode 100644 AGENTS.md create mode 100644 AUTH_API.md create mode 100644 README_ADMIN_API.md create mode 100755 Trash/auth_test_working.sh create mode 100644 WEB_ADMIN_SPECIFICATION.md create mode 100644 admin_specification.md create mode 100644 build/admin_api.o create mode 100644 db/ginxsom.db.backup.1756994126 create mode 100644 scripts/README.md create mode 100755 scripts/generate_config.sh create mode 100755 scripts/setup.sh create mode 100755 scripts/test_admin.sh create mode 100644 src/admin_api.c create mode 100644 src/admin_api.h create mode 100755 tests/admin_test.sh create mode 100755 tests/auth_test.sh create mode 100644 tests/auth_test_tmp/auth_disabled.txt create mode 100644 tests/auth_test_tmp/blacklisted_file.txt create mode 100644 tests/auth_test_tmp/blacklisted_upload.txt create mode 100644 tests/auth_test_tmp/corrupted_sig.txt create mode 100644 tests/auth_test_tmp/expired_event.txt create mode 100644 tests/auth_test_tmp/hash_mismatch.txt create mode 100644 tests/auth_test_tmp/missing_t_tag.txt create mode 100644 tests/auth_test_tmp/missing_x_tag.txt create mode 100644 tests/auth_test_tmp/nonhex_key.txt create mode 100644 tests/auth_test_tmp/random_upload.txt create mode 100644 tests/auth_test_tmp/short_key.txt create mode 100644 tests/auth_test_tmp/whitelisted_upload.txt create mode 100644 tests/auth_test_tmp/wrong_kind.txt create mode 100755 tests/init_admin.sh create mode 100755 tests/simple_auth_test.sh create mode 100755 tests/simple_comprehensive_test.sh create mode 100644 tests/tests/auth_test_tmp/auth_disabled.txt create mode 100644 tests/tests/auth_test_tmp/blacklisted_file.txt create mode 100644 tests/tests/auth_test_tmp/blacklisted_upload.txt create mode 100644 tests/tests/auth_test_tmp/corrupted_sig.txt create mode 100644 tests/tests/auth_test_tmp/expired_event.txt create mode 100644 tests/tests/auth_test_tmp/hash_mismatch.txt create mode 100644 tests/tests/auth_test_tmp/missing_t_tag.txt create mode 100644 tests/tests/auth_test_tmp/missing_x_tag.txt create mode 100644 tests/tests/auth_test_tmp/nonhex_key.txt create mode 100644 tests/tests/auth_test_tmp/random_upload.txt create mode 100644 tests/tests/auth_test_tmp/short_key.txt create mode 100644 tests/tests/auth_test_tmp/whitelisted_upload.txt create mode 100644 tests/tests/auth_test_tmp/wrong_kind.txt diff --git a/.admin_keys b/.admin_keys new file mode 100644 index 0000000..bcd7d85 --- /dev/null +++ b/.admin_keys @@ -0,0 +1,4 @@ +ADMIN_PRIVKEY='31d3fd4bb38f4f6b60fb66e0a2e5063703bb3394579ce820d5aaf3773b96633f' +ADMIN_PUBKEY='bd109762a8185716ec0fe0f887e911c30d40e36cf7b6bb99f6eef3301e9f6f99' +SERVER_PRIVKEY='c4e0d2ed7d36277d6698650f68a6e9199f91f3abb476a67f07303e81309c48f1' +SERVER_PUBKEY='52e366edfa4e9cc6a6d4653828e51ccf828a2f5a05227d7a768f33b5a198681a' diff --git a/.roo/rules-architect/AGENTS.md b/.roo/rules-architect/AGENTS.md new file mode 100644 index 0000000..553e539 --- /dev/null +++ b/.roo/rules-architect/AGENTS.md @@ -0,0 +1,16 @@ +# AGENTS.md + +This file provides guidance to agents when working with code in this repository. + +## Critical Architecture Rules (Non-Obvious Only) + +- **Hybrid Request Handling**: GET requests served directly by nginx from disk, HEAD/PUT/DELETE go through FastCGI +- **Database vs Filesystem**: Database is authoritative for blob existence - filesystem is just storage medium +- **Two-Phase Authentication**: Nostr event validation PLUS Blossom protocol validation (kind 24242 + method tags) +- **Config Architecture**: File-based signed events override database config - enables cryptographic config verification +- **Memory-Only Secrets**: Server private keys never persisted to database - stored in process memory only +- **Extension Decoupling**: File storage uses MIME-based extensions, URL serving accepts any extension via nginx wildcards +- **FastCGI Socket Communication**: nginx communicates with C app via Unix socket, not TCP - affects deployment +- **Authentication Rules Engine**: Optional rules system with priority-based evaluation and caching layer +- **Blob Descriptor Format**: Returns NIP-94 compliant metadata with canonical URLs based on configured origin +- **Admin API Isolation**: Admin endpoints use separate authentication from blob operations - different event structures \ No newline at end of file diff --git a/.roo/rules-ask/AGENTS.md b/.roo/rules-ask/AGENTS.md new file mode 100644 index 0000000..8b564c6 --- /dev/null +++ b/.roo/rules-ask/AGENTS.md @@ -0,0 +1,16 @@ +# AGENTS.md + +This file provides guidance to agents when working with code in this repository. + +## Critical Documentation Context (Non-Obvious Only) + +- **"FastCGI App"**: This is NOT a web server - it's a FastCGI application that nginx calls for dynamic operations +- **Two Config Systems**: File-based config (XDG) is priority 1, database config is fallback - don't assume standard config locations +- **Blob Storage Strategy**: Files stored WITH extensions but URLs accept any extension - counterintuitive to typical web serving +- **Admin API Auth**: Uses Nostr cryptographic events (kind 24242) not standard bearer tokens or sessions +- **Database Schema**: `blobs` table stores metadata, physical files in `blobs/` directory - database is authoritative +- **Build Requirements**: Requires local SQLite build, nostr_core_lib submodule, and specific FastCGI libraries +- **Testing Setup**: Tests require `nak` tool for Nostr event generation - not standard HTTP testing +- **Development Ports**: Local development uses port 9001, production typically uses nginx proxy on standard ports +- **Setup Wizard**: Interactive setup creates cryptographically signed config files - not typical config generation +- **Extension Handling**: nginx config uses wildcards to serve files regardless of URL extension - Blossom protocol compliance \ No newline at end of file diff --git a/.roo/rules-code/AGENTS.md b/.roo/rules-code/AGENTS.md new file mode 100644 index 0000000..79b00e0 --- /dev/null +++ b/.roo/rules-code/AGENTS.md @@ -0,0 +1,18 @@ +# AGENTS.md + +This file provides guidance to agents when working with code in this repository. + +## Critical Coding Rules (Non-Obvious Only) + +- **nostr_core_lib Integration**: Must use `nostr_sha256()` and `nostr_bytes_to_hex()` from nostr_core, NOT standard crypto libs +- **Database Connection Pattern**: Always use `sqlite3_open_v2()` with `SQLITE_OPEN_READONLY` or `SQLITE_OPEN_READWRITE` flags +- **Memory Management**: File data buffers must be freed after use - common pattern is `malloc()` for upload data, `free()` on all paths +- **Error Handling**: FastCGI responses must use `printf("Status: XXX\r\n")` format, NOT standard HTTP response format +- **String Safety**: Always null-terminate strings from SQLite results - use `strncpy()` with size-1 and explicit null termination +- **Hash Validation**: SHA-256 hashes must be exactly 64 hex chars - validate with custom `validate_sha256_format()` function +- **MIME Type Mapping**: Use centralized `mime_to_extension()` function - never hardcode file extensions +- **Authentication**: Nostr event parsing uses cJSON - always call `cJSON_Delete()` after use to prevent memory leaks +- **Configuration Loading**: File config takes priority over database - check XDG paths first, fallback to database +- **Blob Metadata**: Database is single source of truth - use `get_blob_metadata()`, not filesystem checks +- **nostr_core_lib Build**: Uses `build.sh` script, NOT `make` - run `./build.sh` to compile the library +- **Server Testing**: Use `./restart-all.sh` to properly restart and test ginxsom server, NOT direct binary execution \ No newline at end of file diff --git a/.roo/rules-debug/AGENTS.md b/.roo/rules-debug/AGENTS.md new file mode 100644 index 0000000..0533987 --- /dev/null +++ b/.roo/rules-debug/AGENTS.md @@ -0,0 +1,16 @@ +# AGENTS.md + +This file provides guidance to agents when working with code in this repository. + +## Critical Debug Rules (Non-Obvious Only) + +- **FastCGI Socket Issues**: If socket `/tmp/ginxsom-fcgi.sock` exists but connection fails, remove it manually before restart +- **Local SQLite Binary**: Debug with `./sqlite3-build/sqlite3 db/ginxsom.db`, NOT system sqlite3 +- **Authentication Debug**: Failed auth shows error codes in nostr_core format - use `nostr_strerror()` for meanings +- **Memory Leaks**: cJSON objects MUST be deleted after use - common leak source in auth parsing +- **File Permissions**: Blob files need 644 permissions or nginx can't serve them - check with `ls -la blobs/` +- **Database Locks**: SQLite connection must be closed on ALL code paths or database locks occur +- **Config Loading**: File config errors are silent - check stderr for "CONFIG:" messages during startup +- **Admin Key Mismatch**: Database admin_pubkey vs .admin_keys file often cause auth failures +- **Nginx Port Conflicts**: Local nginx on 9001 conflicts with system nginx on 80 - check with `netstat -tlnp` +- **Hash Calculation**: File data buffer must be complete before `nostr_sha256()` call or hash is wrong \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..71cda88 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,43 @@ +# AGENTS.md + +This file provides guidance to agents when working with code in this repository. + +## Critical Project-Specific Rules + +- **Database Path**: Always use `db/ginxsom.db` - this is hardcoded, not configurable +- **SQLite Build**: Uses local SQLite 3.37.2 in `sqlite3-build/` directory, NOT system SQLite +- **Local Development**: Everything runs locally on port 9001, never use system nginx on port 80 +- **FastCGI Socket**: Uses `/tmp/ginxsom-fcgi.sock` for FastCGI communication +- **Config Priority**: File-based config (XDG locations) overrides database config +- **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) + +## Non-Standard Commands + +```bash +# Restart nginx (local only) +./restart-all.sh + +# Start FastCGI daemon +./scripts/start-fcgi.sh + +# Test admin API with authentication +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 + +- FastCGI app handles HEAD/PUT/DELETE requests, nginx serves GET directly from disk +- Two-tier config: File config (signed Nostr events) + database config (key-value) +- Admin API requires Nostr event signatures with specific tag structure +- Database is single source of truth for blob existence (not filesystem) +- Extension handling: URLs work with any extension, files stored with correct extension \ No newline at end of file diff --git a/AUTH_API.md b/AUTH_API.md new file mode 100644 index 0000000..6af850f --- /dev/null +++ b/AUTH_API.md @@ -0,0 +1,491 @@ +# Authentication API Documentation + +## Overview + +The nostr_core_lib unified request validation system provides a comprehensive authentication and authorization framework for Nostr-based applications. It combines Nostr event validation with flexible rule-based authentication in a single API call. + +## Authentication Flow and Order of Operations + +### Authentication Flow Diagram + +``` +┌─────────────────────┐ +│ Request Received │ +└──────────┬──────────┘ + │ + ▼ + ┌─────────────┐ ╔═══════════════════╗ + │ Input Valid?├─No─►║ REJECT: Invalid ║ + └──────┬──────┘ ║ Input (~1μs) ║ + │Yes ╚═══════════════════╝ + ▼ + ┌─────────────┐ ╔═══════════════════╗ + │System Init? ├─No─►║ REJECT: Not ║ + └──────┬──────┘ ║ Initialized ║ + │Yes ╚═══════════════════╝ + ▼ + ┌─────────────┐ + │Auth Header? │ + └──────┬──────┘ + │Yes + ▼ ┌─────────────────────┐ + ┌─────────────┐ No │ │ + │Parse Header ├────────────┤ Skip Nostr │ + └──────┬──────┘ │ Validation │ + │ │ │ + ▼ └──────────┬──────────┘ + ┌─────────────┐ ╔═══════════════════╗ │ + │Valid Base64?├─No─►║ REJECT: Malformed ║ │ + └──────┬──────┘ ║ Header (~10μs) ║ │ + │Yes ╚═══════════════════╝ │ + ▼ │ + ┌─────────────┐ ╔═══════════════════╗ │ + │Valid JSON? ├─No─►║ REJECT: Invalid ║ │ + └──────┬──────┘ ║ JSON (~50μs) ║ │ + │Yes ╚═══════════════════╝ │ + ▼ │ + ┌─────────────┐ ╔═══════════════════╗ │ + │Valid Struct?├─No─►║ REJECT: Invalid ║ │ + └──────┬──────┘ ║ Structure (~100μs)║ │ + │Yes ╚═══════════════════╝ │ + ▼ │ + ┌─────────────────┐ ╔═══════════════════╗ │ + │ ECDSA Signature │No ║ REJECT: Invalid ║ │ + │ Verify (~2ms) ├──►║ Signature (~2ms) ║ │ + └─────────┬───────┘ ╚═══════════════════╝ │ + │Yes │ + ▼ │ + ┌─────────────────┐ ╔═══════════════════╗ │ + │Operation Match? │No ║ REJECT: Unauth. ║ | + └─────────┬───────┘ ║ Operation (~200μs)║ │ + │Yes ╚═══════════════════╝ │ + ▼ │ + ┌─────────────────┐ ╔═══════════════════╗ │ + │Event Expired? │Yes║ REJECT: Expired ║ │ + └─────────┬───────┘ ║ Event (~50μs) ║ │ + │No ╚═══════════════════╝ │ + ▼ │ + ┌─────────────────┐ │ + │Extract Pubkey │ │ + └─────────┬───────┘ │ + │ │ + ▼◄───────────────────────────────────────┘ + ┌─────────────────┐ ╔═══════════════════╗ + │Auth Rules │ No ║ ALLOW: Rules ║ + │Enabled? ├────►║ Disabled ║ + └─────────┬───────┘ ╚═══════════════════╝ + │Yes + ▼ + ┌─────────────────┐ + │Generate Cache │ + │Key (SHA-256) │ + └─────────┬───────┘ + │ + ▼ + ┌─────────────────┐ ╔═══════════════════╗ + │Cache Hit? │ Yes ║ RETURN: Cached ║ + │(~100μs lookup) ├────►║ Decision (~100μs) ║ + └─────────┬───────┘ ╚═══════════════════╝ + │No + ▼ + ╔═══════════════════════════════════════╗ + ║ RULE EVALUATION ENGINE ║ + ║ (Priority Order) ║ + ╚═══════════════════════════════════════╝ + │ + ▼ + ┌─────────────────┐ ╔═══════════════════╗ + │1. Pubkey │ Yes ║ DENY: Pubkey ║ + │ Blacklisted? ├────►║ Blocked ║ + └─────────┬───────┘ ╚═══════════════════╝ + │No + ▼ + ┌─────────────────┐ ╔═══════════════════╗ + │2. Hash │ Yes ║ DENY: Hash ║ + │ Blacklisted? ├────►║ Blocked ║ + └─────────┬───────┘ ╚═══════════════════╝ + │No + ▼ + ┌─────────────────┐ ╔═══════════════════╗ + │3. MIME Type │ Yes ║ DENY: MIME ║ + │ Blacklisted? ├────►║ Blocked ║ + └─────────┬───────┘ ╚═══════════════════╝ + │No + ▼ + ┌─────────────────┐ ╔═══════════════════╗ + │4. Size Limit │ Yes ║ DENY: File ║ + │ Exceeded? ├────►║ Too Large ║ + └─────────┬───────┘ ╚═══════════════════╝ + │No + ▼ + ┌─────────────────┐ ╔═══════════════════╗ + │5. Pubkey │ Yes ║ ALLOW: Pubkey ║ + │ Whitelisted? ├────►║ Whitelisted ║ + └─────────┬───────┘ ╚═══════════════════╝ + │No + ▼ + ┌─────────────────┐ ╔═══════════════════╗ + │6. MIME Type │ Yes ║ ALLOW: MIME ║ + │ Whitelisted? ├────►║ Whitelisted ║ + └─────────┬───────┘ ╚═══════════════════╝ + │No + ▼ + ┌─────────────────┐ ╔═══════════════════╗ + │Whitelist Rules │ Yes ║ DENY: Not in ║ + │Exist? ├────►║ Whitelist ║ + └─────────┬───────┘ ╚═══════════════════╝ + │No + ▼ + ╔═══════════════════╗ + ║ ALLOW: Default ║ + ║ Allow Policy ║ + ╚═══════════════════╝ + │ + ▼ + ┌─────────────────┐ + │ Cache Decision │ + │ (5min TTL) │ + └─────────┬───────┘ + │ + ▼ + ┌─────────────────┐ + │ Return Result │ + │ to Application │ + └─────────────────┘ +``` + +### Performance Timeline (ASCII) + +``` +Fast Path (Cache Hit) - Total: ~101μs +┌─────┬─────────────────────────────────────────────────────────────────┬──────┐ +│ 1μs │ 100μs Cache Lookup │ 1μs │ +└─────┴─────────────────────────────────────────────────────────────────┴──────┘ +Input │ │ Return +Valid │ SQLite SELECT │ Result + +Typical Path (Valid Request) - Total: ~2.4ms +┌──┬───┬────┬─────────────────────────┬────────┬────┬──┐ +│1μ│50μ│100μ│ 2000μs │ 200μs │100μ│1μ│ +└──┴───┴────┴─────────────────────────┴────────┴────┴──┘ +│ │ │ │ │ │ │ +│ │ │ │ ECDSA Signature │ Rule │Cache│Return +│ │ │ │ Verification │ Eval │Store│Result +│ │ │ │ (Most Expensive) │ │ │ +│ │ │ +│ │ JSON Parse +│ Header Parse +Input Validation + +Worst Case (Full Validation) - Total: ~2.7ms +┌──┬───┬────┬─────────────────────────┬─────────┬────┬──┐ +│1μ│50μ│100μ│ 2000μs │ 500μs │100μ│1μ│ +└──┴───┴────┴─────────────────────────┴─────────┴────┴──┘ + │ + All 6 Rule Checks + (Multiple DB Queries) +``` + +### Request Processing Flow (DDoS-Optimized) + +The authentication system is designed with **performance and DDoS protection** as primary concerns. Here's the exact order of operations: + +#### Phase 1: Input Validation (Immediate Rejection) +1. **Null Pointer Checks** - Reject malformed requests instantly (lines 122-128) +2. **Initialization Check** - Verify system is properly initialized +3. **Basic Structure Validation** - Ensure required fields are present + +#### Phase 2: Nostr Event Validation (CPU Intensive) +4. **Authorization Header Parsing** (lines 139-148) + - Extract base64-encoded Nostr event from `Authorization: Nostr ` header + - Decode base64 to JSON (memory allocation + decoding) + - **Early exit**: Invalid base64 or malformed header rejected immediately + +5. **JSON Parsing** (lines 150-156) + - Parse Nostr event JSON using cJSON + - **Early exit**: Invalid JSON rejected before signature verification + +6. **Nostr Event Structure Validation** (lines 159-166) + - Validate event has required fields (kind, pubkey, sig, etc.) + - Check event kind is 24242 for Blossom operations + - **Early exit**: Invalid structure rejected before expensive crypto operations + +7. **Cryptographic Signature Verification** (lines 159-166) + - **Most CPU-intensive operation** - ECDSA signature verification + - Validates event authenticity using secp256k1 + - **Early exit**: Invalid signatures rejected before database queries + +8. **Operation-Specific Validation** (lines 169-178) + - Verify event authorizes the requested operation (upload/delete/list) + - Check required tags (t=operation, x=hash, expiration) + - Validate timestamp and expiration + - **Early exit**: Expired or mismatched events rejected + +9. **Public Key Extraction** (lines 181-184) + - Extract validated public key from event for rule evaluation + +#### Phase 3: Authentication Rules (Database Queries) +10. **Rules System Check** (line 191) + - Quick config check if authentication rules are enabled + - **Early exit**: If disabled, allow request immediately + +11. **Cache Lookup** (lines 1051-1054) + - Generate SHA-256 cache key from request parameters + - Check SQLite cache for previous decision + - **Early exit**: Cache hit returns cached decision (5-minute TTL) + +12. **Rule Evaluation** (Priority Order - lines 1061-1094): + - **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 + +13. **Whitelist Default Denial** (lines 1097-1121) + - If whitelist rules exist but none matched, deny request + - Prevents whitelist bypass attacks + +14. **Cache Storage** (line 1124) + - Store decision in cache for future requests (5-minute TTL) + +### DDoS Protection Features + +#### **Fail-Fast Design** +- **Input validation** happens before any expensive operations +- **Authorization header parsing** fails fast on malformed data +- **JSON parsing** rejects invalid data before signature verification +- **Structure validation** happens before cryptographic operations + +#### **Expensive Operations Last** +- **Signature verification** only after structure validation +- **Database queries** only after successful Nostr validation +- **Cache prioritized** over database queries + +#### **Caching Strategy** +- **SHA-256 cache keys** prevent cache pollution attacks +- **5-minute TTL** balances performance with rule changes +- **LRU eviction** prevents memory exhaustion +- **Per-request caching** includes all parameters (pubkey, operation, hash, MIME, size) + +#### **Resource Limits** +- **JSON parsing** limited to 4KB buffer size +- **Cache entries** limited to prevent memory exhaustion +- **Database connection pooling** (single connection with proper cleanup) +- **String length limits** on all inputs + +#### **Attack Mitigation** +- **Base64 bombs** - Limited decode buffer size (4KB) +- **JSON bombs** - cJSON library handles malformed JSON safely +- **Cache poisoning** - Cryptographic cache keys prevent collisions +- **Rule bypass** - Whitelist default denial prevents unauthorized access +- **Replay attacks** - Timestamp and expiration validation +- **Hash collision attacks** - Full SHA-256 verification + +### Performance Characteristics + +#### **Best Case** (Cached Decision): +1. Input validation: ~1μs +2. Cache lookup: ~100μs (SQLite SELECT) +3. **Total: ~101μs** + +#### **Worst Case** (Full Validation + Rule Evaluation): +1. Input validation: ~1μs +2. Base64 decoding: ~50μs +3. JSON parsing: ~100μs +4. Signature verification: ~2000μs (ECDSA) +5. Database queries: ~500μs (6 rule checks) +6. Cache storage: ~100μs +7. **Total: ~2751μs (~2.7ms)** + +#### **Typical Case** (Valid Request, Rules Enabled): +1. Full validation: ~2200μs +2. Cache miss, 2-3 rule checks: ~200μs +3. **Total: ~2400μs (~2.4ms)** + +### Security Order Rationale + +The rule evaluation order is specifically designed for security: + +1. **Blacklists First** - Immediate denial of known bad actors +2. **Resource Limits** - Prevent resource exhaustion attacks +3. **Whitelists Last** - Only allow after passing all security checks +4. **Default Deny** - If whitelists exist but don't match, deny + +This ensures that even if an attacker bypasses one layer, subsequent layers will catch the attack. + +## Core API + +### Primary Function + +```c +int nostr_validate_request(nostr_request_t* request, nostr_request_result_t* result); +``` + +This single function handles: +- Nostr event signature validation +- Event structure validation (required fields, timestamps) +- Authentication rule evaluation +- Public key extraction and validation + +### Request Structure + +```c +typedef struct { + const char* event_json; // Raw Nostr event JSON + const char* app_id; // Application identifier ("ginxsom", "c-relay") + const char* operation; // Operation type ("upload", "delete", "list") + const char* content_hash; // SHA-256 hash for file operations (optional) + const char* mime_type; // MIME type for upload operations (optional) + size_t content_size; // File size for upload operations (0 if N/A) +} nostr_request_t; +``` + +### Result Structure + +```c +typedef struct { + int is_valid; // 1 if request is valid, 0 otherwise + int error_code; // Specific error code (see Error Codes) + char error_message[512]; // Human-readable error description + char pubkey[65]; // Extracted public key (hex, null-terminated) + time_t timestamp; // Event timestamp + char event_id[65]; // Event ID (hex, null-terminated) +} nostr_request_result_t; +``` + +## Authentication Rules System + +The system supports priority-based authentication rules that are evaluated in order: + +### Rule Types + +1. **NOSTR_AUTH_RULE_PUBKEY_WHITELIST** - Allow specific public keys +2. **NOSTR_AUTH_RULE_PUBKEY_BLACKLIST** - Block specific public keys +3. **NOSTR_AUTH_RULE_HASH_BLACKLIST** - Block specific content hashes +4. **NOSTR_AUTH_RULE_MIME_RESTRICTION** - Restrict allowed MIME types +5. **NOSTR_AUTH_RULE_SIZE_LIMIT** - Enforce maximum file sizes + +### Rule Evaluation + +- Rules are processed by priority (lower numbers = higher priority) +- First matching rule determines the outcome +- ALLOW rules permit the request +- DENY rules reject the request +- If no rules match, the default action is ALLOW + +### Rule Caching + +The system includes an intelligent caching mechanism: +- LRU (Least Recently Used) eviction policy +- Configurable cache size (default: 1000 entries) +- Cache keys based on pubkey + operation + content hash +- Automatic cache invalidation when rules change + +## Database Backend + +### Pluggable Architecture + +The system uses a pluggable database backend interface: + +```c +typedef struct { + int (*init)(const char* connection_string, void** context); + int (*get_rules)(void* context, const char* app_id, + nostr_auth_rule_t** rules, int* count); + int (*cleanup)(void* context); +} nostr_db_backend_t; +``` + +### SQLite Implementation + +Default implementation uses SQLite with the following schema: + +```sql +-- Authentication rules table (per application) +CREATE TABLE auth_rules_[APP_ID] ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + priority INTEGER NOT NULL, + rule_type INTEGER NOT NULL, + action INTEGER NOT NULL, + pattern TEXT, + value_int INTEGER, + created_at INTEGER DEFAULT (strftime('%s', 'now')), + updated_at INTEGER DEFAULT (strftime('%s', 'now')) +); +``` + +## Error Codes + +The system uses specific error codes for different failure scenarios: + +- **-50**: `NOSTR_AUTH_ERROR_INVALID_EVENT` - Malformed Nostr event +- **-51**: `NOSTR_AUTH_ERROR_INVALID_SIGNATURE` - Invalid event signature +- **-52**: `NOSTR_AUTH_ERROR_PUBKEY_BLOCKED` - Public key is blacklisted +- **-53**: `NOSTR_AUTH_ERROR_HASH_BLOCKED` - Content hash is blacklisted +- **-54**: `NOSTR_AUTH_ERROR_MIME_RESTRICTED` - MIME type not allowed +- **-55**: `NOSTR_AUTH_ERROR_SIZE_EXCEEDED` - File size limit exceeded + +## Usage Examples + +### Basic Validation + +```c +#include "nostr_core/request_validator.h" + +// Initialize the system (once per application) +int result = nostr_request_validator_init("db/myapp.db", "myapp"); +if (result != 0) { + fprintf(stderr, "Failed to initialize validator: %d\n", result); + return -1; +} + +// Validate a request +nostr_request_t request = { + .event_json = "{\"kind\":24242,\"pubkey\":\"abc123...\",\"sig\":\"def456...\"}", + .app_id = "myapp", + .operation = "upload", + .content_hash = "sha256hash...", + .mime_type = "text/plain", + .content_size = 1024 +}; + +nostr_request_result_t result; +int status = nostr_validate_request(&request, &result); + +if (result.is_valid) { + printf("Request authorized for pubkey: %s\n", result.pubkey); +} else { + printf("Request denied: %s (code: %d)\n", result.error_message, result.error_code); +} +``` + +### Ginxsom Integration + +The ginxsom application has been updated to use this system: + +```c +// Replace old authenticate_request_with_rules() calls with: +nostr_request_t auth_request = { + .event_json = event_json, + .app_id = "ginxsom", + .operation = "upload", // or "list", "delete" + .content_hash = calculated_hash, + .mime_type = detected_mime_type, + .content_size = file_size +}; + +nostr_request_result_t auth_result; +int auth_status = nostr_validate_request(&auth_request, &auth_result); + +if (!auth_result.is_valid) { + printf("Status: 403\r\n"); + printf("Content-Type: application/json\r\n\r\n"); + printf("{\"error\":\"Authentication failed\",\"message\":\"%s\"}\n", + auth_result.error_message); + return; +} + +// Use auth_result.pubkey for the authenticated public key +``` + diff --git a/Makefile b/Makefile index 002cebf..5236473 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ BUILDDIR = build TARGET = $(BUILDDIR)/ginxsom-fcgi # Source files -SOURCES = $(SRCDIR)/main.c +SOURCES = $(SRCDIR)/main.c $(SRCDIR)/admin_api.c OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(BUILDDIR)/%.o) # Default target diff --git a/README_ADMIN_API.md b/README_ADMIN_API.md new file mode 100644 index 0000000..8bbbd57 --- /dev/null +++ b/README_ADMIN_API.md @@ -0,0 +1,219 @@ +# Ginxsom Admin API + +A Nostr-compliant admin interface for the Ginxsom Blossom server that provides programmatic access to server statistics, configuration, and file management operations. + +## Overview + +The admin API allows server administrators to: +- View server statistics (file counts, storage usage, user metrics) +- Retrieve and update server configuration settings +- Browse recent uploaded files with pagination +- Monitor server health and disk usage + +All operations require Nostr authentication using admin-authorized public keys. + +## Quick Start + +### 1. Configure Admin Access + +Add your admin pubkey to the server configuration: + +```bash +# Generate admin keys (keep private key secure!) +ADMIN_PRIVKEY=$(nak key generate) +ADMIN_PUBKEY=$(echo "$ADMIN_PRIVKEY" | nak key public) + +# Configure server +sqlite3 db/ginxsom.db << EOF +INSERT OR REPLACE INTO server_config (key, value, description) VALUES + ('admin_pubkey', '$ADMIN_PUBKEY', 'Nostr public key authorized for admin operations'), + ('admin_enabled', 'true', 'Enable admin interface'); +EOF +``` + +### 2. Build and Start Server + +```bash +make clean && make +spawn-fcgi -s /tmp/ginxsom-fcgi.sock -n ./build/ginxsom-fcgi +nginx -c $(pwd)/config/local-nginx.conf +``` + +### 3. Test the API + +```bash +# Run the complete test suite +./tests/admin_test.sh + +# Or test individual endpoints +export ADMIN_PRIVKEY="your_private_key_here" +./tests/admin_test.sh +``` + +## API Endpoints + +### GET /api/health +System health check (no authentication required). +```bash +curl http://localhost:9001/api/health +``` + +### GET /api/stats +Server statistics and metrics. +```json +{ + "status": "success", + "data": { + "total_files": 1234, + "total_bytes": 104857600, + "total_size_mb": 100.0, + "unique_uploaders": 56, + "avg_file_size": 85049, + "file_types": { + "image/png": 45, + "image/jpeg": 32 + } + } +} +``` + +### GET /api/config +Current server configuration. +```json +{ + "status": "success", + "data": { + "max_file_size": "104857600", + "require_auth": "false", + "server_name": "ginxsom", + "nip94_enabled": "true" + } +} +``` + +### PUT /api/config +Update server configuration. +```json +{ + "max_file_size": "209715200", + "require_auth": "true", + "nip94_enabled": "true" +} +``` + +### GET /api/files +Recent uploaded files with pagination. +```json +{ + "status": "success", + "data": { + "files": [ + { + "sha256": "abc123...", + "size": 184292, + "type": "application/pdf", + "uploaded_at": 1725105921, + "uploader_pubkey": "def456...", + "url": "http://localhost:9001/abc123.pdf" + } + ], + "total": 1234, + "limit": 50, + "offset": 0 + } +} +``` + +## Manual API Usage with nak and curl + +### Generate Admin Authentication Event + +```bash +# Create an authenticated event +EVENT=$(nak event -k 24242 -c "admin_request" \ + --tag t="GET" \ + --tag expiration="$(date -d '+1 hour' +%s)" \ + --sec "$ADMIN_PRIVKEY") + +# Send authenticated request +AUTH_HEADER="Nostr $(echo "$EVENT" | base64 -w 0)" +curl -H "Authorization: $AUTH_HEADER" http://localhost:9001/api/stats +``` + +### Update Configuration + +```bash +# Create PUT event (method in tag) +EVENT=$(nak event -k 24242 -c "admin_request" \ + --tag t="PUT" \ + --tag expiration="$(date -d '+1 hour' +%s)" \ + --sec "$ADMIN_PRIVKEY") + +AUTH_HEADER="Nostr $(echo "$EVENT" | base64 -w 0)" + +curl -X PUT -H "Authorization: $AUTH_HEADER" \ + -H "Content-Type: application/json" \ + -d '{"max_file_size": "209715200", "require_auth": "true"}' \ + http://localhost:9001/api/config +``` + +## Security Features + +- **Nostr Authentication**: All admin operations require valid Nostr kind 24242 events +- **Pubkey Verification**: Only events signed by configured admin pubkeys are accepted +- **Event Expiration**: Admin events must include expiration timestamps for security +- **Access Control**: Separate enable/disable flag for admin interface + +## Development and Testing + +### Prerequisites +- nak (https://github.com/fiatjaf/nak) +- curl, jq +- sqlite3 + +### Run Tests +```bash +# Make test script executable +chmod +x tests/admin_test.sh + +# Run complete test suite +./tests/admin_test.sh + +# Run with specific admin key +export ADMIN_PRIVKEY="your_private_key" +./tests/admin_test.sh +``` + +### Build System +```bash +# Clean build +make clean + +# Build with debug info +make debug + +# Run FastCGI process +make run +``` + +## Files Added/Modified + +- `src/admin_api.h` - Admin API function declarations +- `src/admin_api.c` - Complete admin API implementation +- `src/main.c` - Updated with admin API routing +- `config/local-nginx.conf` - Updated with admin API routes +- `tests/admin_test.sh` - Complete test suite +- `Makefile` - Updated to compile admin_api.c +- `README_ADMIN_API.md` - This documentation + +## Future Enhancements + +- **Nostr Relay Integration**: Automatic relay subscription for remote admin control +- **Admin Pubkey Rotation**: Support for multiple admin keys and key rotation +- **Audit Logging**: Detailed logging of admin operations +- **Rate Limiting**: Prevent abuse of admin endpoints +- **Web Dashboard**: Optional HTML/CSS/JavaScript frontend + +--- + +The admin API provides a secure, Nostr-compliant interface for server management through command-line tools while maintaining full compatibility with the existing Blossom protocol implementation. \ No newline at end of file diff --git a/Trash/auth_test_working.sh b/Trash/auth_test_working.sh new file mode 100755 index 0000000..6e84635 --- /dev/null +++ b/Trash/auth_test_working.sh @@ -0,0 +1,266 @@ +#!/bin/bash + +# Working Authentication System Test Suite +# Tests the unified nostr_core_lib authentication system integrated into ginxsom + +# Configuration +SERVER_URL="http://localhost:9001" +UPLOAD_ENDPOINT="${SERVER_URL}/upload" +LIST_ENDPOINT="${SERVER_URL}/list" +DELETE_ENDPOINT="${SERVER_URL}/delete" +DB_PATH="db/ginxsom.db" +TEST_DIR="tests/auth_test_tmp" + +# Test keys for different scenarios +TEST_ADMIN_PRIVKEY="993bf9c54fc00bd32a5a1ce64b6d384a5fce109df1e9aee9be1052c1e5cd8120" +TEST_ADMIN_PUBKEY="2ef05348f28d24e0f0ed0751278442c27b62c823c37af8d8d89d8592c6ee84e7" + +TEST_USER1_PRIVKEY="5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a" +TEST_USER1_PUBKEY="79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + +TEST_USER2_PRIVKEY="182c3a5e3b7a1b7e4f5c6b7c8b4a5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2" +TEST_USER2_PUBKEY="c95195e5e7de1ad8c4d3c0ac4e8b5c0c4e0c4d3c1e5c8d4c2e7e9f4a5b6c7d8e" + +# Test counters +TESTS_PASSED=0 +TESTS_FAILED=0 +TESTS_TOTAL=0 + +echo "=== Ginxsom Authentication System Test Suite ===" +echo "Testing unified nostr_core_lib authentication integration" +echo "Timestamp: $(date -Iseconds)" +echo + +# Helper functions +test_pass() { + local test_name="$1" + ((TESTS_PASSED++)) + ((TESTS_TOTAL++)) + echo "✓ $test_name" +} + +test_fail() { + local test_name="$1" + local reason="$2" + ((TESTS_FAILED++)) + ((TESTS_TOTAL++)) + echo "✗ $test_name: $reason" +} + +# Check prerequisites +echo "[INFO] Checking prerequisites..." +for cmd in nak curl jq sqlite3; do + if ! command -v $cmd &> /dev/null; then + echo "[ERROR] $cmd command not found" + exit 1 + fi +done + +# Check if server is running +if ! curl -s -f "${SERVER_URL}/" > /dev/null 2>&1; then + echo "[ERROR] Server not running at $SERVER_URL" + echo "[INFO] Start with: ./restart-all.sh" + exit 1 +fi + +# Check if database exists +if [[ ! -f "$DB_PATH" ]]; then + echo "[ERROR] Database not found at $DB_PATH" + exit 1 +fi + +echo "[SUCCESS] All prerequisites met" + +# Setup test environment +echo "[INFO] Setting up test environment..." +mkdir -p "$TEST_DIR" + +# Enable authentication rules in database +sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO auth_config (key, value) VALUES ('auth_rules_enabled', 'true');" + +# Clear any existing test rules +sqlite3 "$DB_PATH" "DELETE FROM auth_rules WHERE description LIKE 'TEST_%' ESCAPE '\';" 2>/dev/null || true +sqlite3 "$DB_PATH" "DELETE FROM auth_cache;" 2>/dev/null || true + +echo "[SUCCESS] Test environment ready" + +# Generate test file +create_test_file() { + local filename="$1" + local size="${2:-1024}" + local filepath="$TEST_DIR/$filename" + + if [[ $size -lt 100 ]]; then + echo "Small test file $(date)" > "$filepath" + else + echo "Test file: $filename" > "$filepath" + echo "Created: $(date -Iseconds)" >> "$filepath" + echo "Size target: $size bytes" >> "$filepath" + dd if=/dev/urandom bs=1 count=$((size - 200)) 2>/dev/null | base64 >> "$filepath" + fi + + echo "$filepath" +} + +# Generate nostr event for authentication +create_auth_event() { + local privkey="$1" + local operation="$2" + local hash="$3" + local expiration_offset="${4:-3600}" # 1 hour default + + local expiration=$(date -d "+${expiration_offset} seconds" +%s) + + local event_args=(-k 24242 -c "" --tag "t=$operation" --tag "expiration=$expiration" --sec "$privkey") + + if [[ -n "$hash" ]]; then + event_args+=(--tag "x=$hash") + fi + + nak event "${event_args[@]}" +} + +# Test authenticated upload +test_authenticated_upload() { + local privkey="$1" + local operation="$2" + local file_path="$3" + local expected_status="$4" + local test_description="$5" + + echo "[TEST] $test_description" + + local file_hash=$(sha256sum "$file_path" | cut -d' ' -f1) + local event=$(create_auth_event "$privkey" "$operation" "$file_hash") + local auth_header="Nostr $(echo "$event" | base64 -w 0)" + + local mime_type=$(file -b --mime-type "$file_path" 2>/dev/null || echo "application/octet-stream") + + local response_file=$(mktemp) + local http_status=$(curl -s -w "%{http_code}" \ + -H "Authorization: $auth_header" \ + -H "Content-Type: $mime_type" \ + --data-binary "@$file_path" \ + -X PUT "$UPLOAD_ENDPOINT" \ + -o "$response_file") + + local response_body=$(cat "$response_file") + rm -f "$response_file" + + if [[ "$http_status" == "$expected_status" ]]; then + test_pass "$test_description (HTTP $http_status)" + return 0 + else + test_fail "$test_description" "Expected HTTP $expected_status, got $http_status. Response: $response_body" + return 1 + fi +} + +# Add auth rule to database +add_auth_rule() { + local rule_type="$1" + local target="$2" + local operation="${3:-*}" + local priority="${4:-100}" + local description="${5:-TEST_RULE}" + + sqlite3 "$DB_PATH" "INSERT INTO auth_rules (rule_type, rule_target, operation, priority, enabled, description) + VALUES ('$rule_type', '$target', '$operation', $priority, 1, '$description');" +} + +# Clear test rules +clear_auth_rules() { + sqlite3 "$DB_PATH" "DELETE FROM auth_rules WHERE description LIKE 'TEST_%' ESCAPE '\';" 2>/dev/null || true + sqlite3 "$DB_PATH" "DELETE FROM auth_cache;" 2>/dev/null || true +} + +echo +echo "=== Test 1: Basic Authentication (Disabled) ===" + +# Disable auth rules temporarily +sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO auth_config (key, value) VALUES ('auth_rules_enabled', 'false');" + +test_file1=$(create_test_file "basic_test.txt" 500) +test_authenticated_upload "$TEST_USER1_PRIVKEY" "upload" "$test_file1" "200" "Upload without auth rules (disabled)" + +# Re-enable auth rules for other tests +sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO auth_config (key, value) VALUES ('auth_rules_enabled', 'true');" + +echo +echo "=== Test 2: Pubkey Whitelist Rules ===" + +clear_auth_rules +add_auth_rule "pubkey_whitelist" "$TEST_USER1_PUBKEY" "upload" 10 "TEST_WHITELIST_USER1" + +test_file2=$(create_test_file "whitelist_test.txt" 500) +test_authenticated_upload "$TEST_USER1_PRIVKEY" "upload" "$test_file2" "200" "Whitelisted user upload" +test_authenticated_upload "$TEST_USER2_PRIVKEY" "upload" "$test_file2" "403" "Non-whitelisted user upload" + +echo +echo "=== Test 3: Pubkey Blacklist Rules ===" + +clear_auth_rules +add_auth_rule "pubkey_blacklist" "$TEST_USER2_PUBKEY" "upload" 5 "TEST_BLACKLIST_USER2" + +test_file3=$(create_test_file "blacklist_test.txt" 500) +test_authenticated_upload "$TEST_USER1_PRIVKEY" "upload" "$test_file3" "200" "Non-blacklisted user upload" +test_authenticated_upload "$TEST_USER2_PRIVKEY" "upload" "$test_file3" "403" "Blacklisted user upload" + +echo +echo "=== Test 4: Hash Blacklist Rules ===" + +clear_auth_rules +test_file4=$(create_test_file "hash_blacklist_test.txt" 500) +file_hash4=$(sha256sum "$test_file4" | cut -d' ' -f1) + +# Add hash to blacklist +add_auth_rule "hash_blacklist" "$file_hash4" "upload" 5 "TEST_HASH_BLACKLIST" + +test_authenticated_upload "$TEST_USER1_PRIVKEY" "upload" "$test_file4" "403" "Blacklisted hash upload" + +# Upload of different hash should succeed +test_file4b=$(create_test_file "hash_allowed_test.txt" 600) +test_authenticated_upload "$TEST_USER1_PRIVKEY" "upload" "$test_file4b" "200" "Non-blacklisted hash upload" + +echo +echo "=== Test 5: Rule Priority Ordering ===" + +clear_auth_rules + +# Add conflicting rules with different priorities +add_auth_rule "pubkey_blacklist" "$TEST_USER1_PUBKEY" "upload" 5 "TEST_PRIORITY_BLACKLIST" # Higher priority (lower number) +add_auth_rule "pubkey_whitelist" "$TEST_USER1_PUBKEY" "upload" 10 "TEST_PRIORITY_WHITELIST" # Lower priority + +test_file5=$(create_test_file "priority_test.txt" 500) +test_authenticated_upload "$TEST_USER1_PRIVKEY" "upload" "$test_file5" "403" "Priority test (blacklist > whitelist)" + +# Reverse priorities +clear_auth_rules +add_auth_rule "pubkey_whitelist" "$TEST_USER1_PUBKEY" "upload" 5 "TEST_PRIORITY_WHITELIST_HIGH" +add_auth_rule "pubkey_blacklist" "$TEST_USER1_PUBKEY" "upload" 10 "TEST_PRIORITY_BLACKLIST_LOW" + +test_authenticated_upload "$TEST_USER1_PRIVKEY" "upload" "$test_file5" "200" "Priority test (whitelist > blacklist)" + +echo +echo "=== Cleanup ===" + +# Remove temporary files +rm -rf "$TEST_DIR" + +# Clean up test auth rules +clear_auth_rules + +echo +echo "=== Test Results Summary ===" +echo "Tests Passed: $TESTS_PASSED" +echo "Tests Failed: $TESTS_FAILED" +echo "Total Tests: $TESTS_TOTAL" +echo + +if [[ $TESTS_FAILED -eq 0 ]]; then + echo "[SUCCESS] All tests passed! ✓" + exit 0 +else + echo "[ERROR] $TESTS_FAILED tests failed! ✗" + exit 1 +fi \ No newline at end of file diff --git a/WEB_ADMIN_SPECIFICATION.md b/WEB_ADMIN_SPECIFICATION.md new file mode 100644 index 0000000..21609a5 --- /dev/null +++ b/WEB_ADMIN_SPECIFICATION.md @@ -0,0 +1,1088 @@ +# Ginxsom Web Admin Interface - Technical Specification + +## Overview + +A minimal single-page admin interface for ginxsom server management, built with vanilla JavaScript and C-based API endpoints. Uses Nostr-compliant authentication with admin pubkey verification. + +## Architecture + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Web Browser │───▶│ nginx │───▶│ ginxsom FastCGI │ +│ (admin.html) │ │ (static files │ │ + admin_api │ +│ │ │ + API proxy) │ │ │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ SQLite DB │ + │ (ginxsom.db) │ + └─────────────────┘ +``` + +## File Structure + +``` +src/ +├── main.c # Existing main FastCGI application +├── admin_api.c # NEW: Admin API endpoint handlers +├── admin_api.h # NEW: Admin API function declarations +└── ginxsom.h # Existing shared headers + +admin/ +├── admin.html # Single-page admin interface (with inline CSS/JS) +└── README.md # Setup and usage guide + +config/ +└── local-nginx.conf # Updated with admin interface routes +``` + +## Backend Implementation Details + +### 1. admin_api.h Header File + +```c +#ifndef ADMIN_API_H +#define ADMIN_API_H + +#include "ginxsom.h" + +// Main API request handler +void handle_admin_api_request(const char* method, const char* uri); + +// Individual endpoint handlers +void handle_stats_api(void); +void handle_config_get_api(void); +void handle_config_put_api(void); +void handle_files_api(void); +void handle_health_api(void); + +// Admin authentication functions +int authenticate_admin_request(const char* auth_header); +int is_admin_enabled(void); +int verify_admin_pubkey(const char* event_pubkey); + +// Utility functions +void send_json_response(int status, const char* json_content); +void send_json_error(int status, const char* error, const char* message); +int parse_query_params(const char* query_string, char params[][256], int max_params); + +#endif +``` + +### 2. admin_api.c Implementation Structure + +#### API Router Function +```c +void handle_admin_api_request(const char* method, const char* uri) { + const char* path = uri + 4; // Skip "/api" + + // Check if admin interface is enabled + if (!is_admin_enabled()) { + send_json_error(503, "admin_disabled", "Admin interface is disabled"); + return; + } + + // Authentication required for all admin operations except health check + if (strcmp(path, "/health") != 0) { + const char* auth_header = getenv("HTTP_AUTHORIZATION"); + if (!authenticate_admin_request(auth_header)) { + send_json_error(401, "admin_auth_required", "Valid admin authentication required"); + return; + } + } + + if (strcmp(method, "GET") == 0) { + if (strcmp(path, "/stats") == 0) { + handle_stats_api(); + } else if (strcmp(path, "/config") == 0) { + handle_config_get_api(); + } else if (strncmp(path, "/files", 6) == 0) { + handle_files_api(); + } else if (strcmp(path, "/health") == 0) { + handle_health_api(); + } else { + send_json_error(404, "not_found", "API endpoint not found"); + } + } else if (strcmp(method, "PUT") == 0) { + if (strcmp(path, "/config") == 0) { + handle_config_put_api(); + } else { + send_json_error(405, "method_not_allowed", "Method not allowed"); + } + } else { + send_json_error(405, "method_not_allowed", "Method not allowed"); + } +} +``` + +#### Admin Authentication Functions +```c +int authenticate_admin_request(const char* auth_header) { + if (!auth_header) { + return 0; // No auth header + } + + // Use existing authentication system with "admin" method + int auth_result = authenticate_request(auth_header, "admin", NULL); + if (auth_result != NOSTR_SUCCESS) { + return 0; // Invalid Nostr event + } + + // Extract pubkey from validated event using existing parser + char event_json[4096]; + int parse_result = parse_authorization_header(auth_header, event_json, sizeof(event_json)); + if (parse_result != NOSTR_SUCCESS) { + return 0; + } + + cJSON* event = cJSON_Parse(event_json); + if (!event) { + return 0; + } + + cJSON* pubkey_json = cJSON_GetObjectItem(event, "pubkey"); + if (!pubkey_json || !cJSON_IsString(pubkey_json)) { + cJSON_Delete(event); + return 0; + } + + const char* event_pubkey = cJSON_GetStringValue(pubkey_json); + int is_admin = verify_admin_pubkey(event_pubkey); + + cJSON_Delete(event); + return is_admin; +} + +int verify_admin_pubkey(const char* event_pubkey) { + if (!event_pubkey) { + return 0; + } + + sqlite3* db; + sqlite3_stmt* stmt; + int rc, is_admin = 0; + + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc) { + return 0; + } + + const char* sql = "SELECT value FROM server_config WHERE key = 'admin_pubkey'"; + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + const char* admin_pubkey = (const char*)sqlite3_column_text(stmt, 0); + if (admin_pubkey && strcmp(event_pubkey, admin_pubkey) == 0) { + is_admin = 1; + } + } + sqlite3_finalize(stmt); + } + sqlite3_close(db); + + return is_admin; +} + +int is_admin_enabled(void) { + sqlite3* db; + sqlite3_stmt* stmt; + int rc, enabled = 0; + + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc) { + return 0; // Default disabled if can't access DB + } + + const char* sql = "SELECT value FROM server_config WHERE key = 'admin_enabled'"; + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + const char* value = (const char*)sqlite3_column_text(stmt, 0); + enabled = (value && strcmp(value, "true") == 0) ? 1 : 0; + } + sqlite3_finalize(stmt); + } + sqlite3_close(db); + + return enabled; +} +``` + +#### Statistics API Handler +```c +void handle_stats_api(void) { + sqlite3* db; + sqlite3_stmt* stmt; + int rc; + + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc) { + send_json_error(500, "database_error", "Failed to open database"); + return; + } + + // Query storage_stats view + const char* sql = "SELECT total_blobs, total_bytes, avg_blob_size, " + "unique_uploaders, first_upload, last_upload FROM storage_stats"; + + // ... SQLite query implementation + // ... JSON response generation + + sqlite3_close(db); +} +``` + +### 3. main.c Integration Points + +#### Minimal changes to main.c: +```c +#include "admin_api.h" // Add at top + +// In main() request routing section, add this condition: +} else if (strncmp(request_uri, "/api/", 5) == 0) { + // Route API calls to admin handlers + handle_admin_api_request(request_method, request_uri); +``` + +## API Endpoint Specifications + +### GET /api/stats +**Purpose**: Retrieve server statistics and metrics +**Response**: +```json +{ + "status": "success", + "data": { + "total_files": 1234, + "total_bytes": 104857600, + "total_size_mb": 100.0, + "unique_uploaders": 56, + "first_upload": 1693929600, + "last_upload": 1704067200, + "avg_file_size": 85049, + "file_types": { + "image/png": 45, + "image/jpeg": 32, + "application/pdf": 12, + "other": 8 + } + } +} +``` + +### GET /api/config +**Purpose**: Retrieve current server configuration +**Response**: +```json +{ + "status": "success", + "data": { + "max_file_size": "104857600", + "require_auth": "false", + "server_name": "ginxsom", + "nip94_enabled": "true", + "cdn_origin": "http://localhost:9001", + "auth_rules_enabled": "false", + "auth_cache_ttl": "300" + } +} +``` + +### PUT /api/config +**Purpose**: Update server configuration +**Request Body**: +```json +{ + "max_file_size": "209715200", + "require_auth": "true", + "nip94_enabled": "true", + "cdn_origin": "https://cdn.example.com" +} +``` +**Response**: +```json +{ + "status": "success", + "message": "Configuration updated successfully", + "updated_keys": ["max_file_size", "require_auth"] +} +``` + +### GET /api/files?limit=50&offset=0 +**Purpose**: Retrieve recent files with pagination +**Parameters**: +- `limit` (default: 50): Number of files to return +- `offset` (default: 0): Pagination offset +**Response**: +```json +{ + "status": "success", + "data": { + "files": [ + { + "sha256": "b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553", + "size": 184292, + "type": "application/pdf", + "uploaded_at": 1725105921, + "uploader_pubkey": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "filename": "document.pdf", + "url": "http://localhost:9001/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf" + } + ], + "total": 1234, + "limit": 50, + "offset": 0 + } +} +``` + +### GET /api/health +**Purpose**: System health check +**Response**: +```json +{ + "status": "success", + "data": { + "database": "connected", + "blob_directory": "accessible", + "disk_usage": { + "total_bytes": 1073741824, + "used_bytes": 536870912, + "available_bytes": 536870912, + "usage_percent": 50.0 + }, + "server_time": 1704067200, + "uptime": 3600 + } +} +``` + +## Frontend Implementation Details + +### Single File Structure (admin.html) +**Complete self-contained HTML file with inline CSS and JavaScript** + +```html + + + + + + Ginxsom Admin + + + +
+

Ginxsom Admin Dashboard

+
Loading...
+
+ +
+ +
+

Server Statistics

+
+
+

Total Files

+
-
+
+
+

Storage Used

+
-
+
+
+

Unique Users

+
-
+
+
+
+ + +
+

Server Configuration

+
+ +
+ +
+ + +
+

Recent Files

+
+ +
+ +
+
+ + + + +``` + +## nginx Configuration + +### Updated local-nginx.conf additions: +```nginx +# Admin interface (single file) +location /admin { + alias admin/; + try_files $uri /admin.html; +} + +# Admin API endpoints +location /api/ { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/ginxsom.fcgi; + fastcgi_pass fastcgi_backend; + + # CORS headers for admin interface + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods "GET, PUT, OPTIONS"; + add_header Access-Control-Allow-Headers "Content-Type"; +} +``` + +## Database Schema Requirements + +### Existing tables used: +- `blobs` - File metadata and statistics +- `server_config` - Configuration key-value pairs (ENHANCED) +- `storage_stats` - Aggregated statistics view + +### Required server_config additions: +```sql +INSERT OR IGNORE INTO server_config (key, value, description) VALUES + ('admin_pubkey', '', 'Nostr public key authorized for admin operations'), + ('admin_enabled', 'false', 'Enable admin interface (requires admin_pubkey)'); +``` + +## Security Considerations + +1. **Nostr Authentication**: All admin operations require valid Nostr event signatures +2. **Admin Pubkey Verification**: Only events signed by configured admin pubkey are accepted +3. **Event Validation**: Full Nostr event structure and signature verification +4. **Expiration Enforcement**: Admin events must include expiration timestamps +5. **Input Validation**: All config updates validated before DB storage +6. **SQL Injection Prevention**: Prepared statements only +7. **CORS**: Controlled CORS headers for API endpoints +8. **Rate Limiting**: Consider nginx rate limiting for API endpoints + +## Testing Strategy + +1. **Unit Tests**: Test each API endpoint individually +2. **Integration Tests**: Test frontend-backend communication +3. **Manual Testing**: Browser-based UI testing +4. **Performance Tests**: API response times under load + +## Implementation Steps - API First Approach + +1. Create [`admin_api.h`](src/admin_api.h) header file +2. Implement [`admin_api.c`](src/admin_api.c) with all endpoint handlers +3. Add minimal routing code to [`main.c`](src/main.c) +4. Create [`admin_test.sh`](tests/admin_test.sh) script with nak/curl testing +5. Update [`local-nginx.conf`](config/local-nginx.conf) for API routing +6. Test each API endpoint with command-line tools +7. Document admin API usage with practical examples + +## Testing Strategy - Command Line First + +### Admin Test Script (admin_test.sh) + +**Complete test script for admin API using nak and curl:** + +```bash +#!/bin/bash + +# Ginxsom Admin API Test Script +# Tests admin API endpoints using nak (for Nostr events) and curl +# +# Prerequisites: +# - nak: https://github.com/fiatjaf/nak +# - curl, jq +# - Admin pubkey configured in ginxsom server_config + +set -e + +# Configuration +GINXSOM_URL="http://localhost:9001" +ADMIN_PRIVKEY="${ADMIN_PRIVKEY:-}" +ADMIN_PUBKEY="" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Helper functions +log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } +log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } + +check_dependencies() { + log_info "Checking dependencies..." + for cmd in nak curl jq; do + if ! command -v $cmd &> /dev/null; then + log_error "$cmd is not installed" + exit 1 + fi + done + log_success "All dependencies found" +} + +generate_admin_keys() { + if [[ -z "$ADMIN_PRIVKEY" ]]; then + log_info "Generating new admin key pair..." + ADMIN_PRIVKEY=$(nak key generate) + log_warning "Generated admin private key: $ADMIN_PRIVKEY" + log_warning "Save this key: export ADMIN_PRIVKEY='$ADMIN_PRIVKEY'" + fi + + ADMIN_PUBKEY=$(echo "$ADMIN_PRIVKEY" | nak key public) + log_info "Admin public key: $ADMIN_PUBKEY" +} + +create_admin_event() { + local method="$1" + local content="admin_request" + local expiration=$(($(date +%s) + 3600)) # 1 hour from now + + # Create Nostr event with nak + local event=$(nak event -k 24242 -c "$content" \ + --tag t="$method" \ + --tag expiration="$expiration" \ + --sec "$ADMIN_PRIVKEY") + + echo "$event" +} + +send_admin_request() { + local method="$1" + local endpoint="$2" + local data="$3" + + log_info "Testing $method $endpoint" + + # Create authenticated Nostr event + local event=$(create_admin_event "$method") + local auth_header="Nostr $(echo "$event" | base64 -w 0)" + + # Send request with curl + local curl_args=(-s -w "%{http_code}" -H "Authorization: $auth_header") + + if [[ "$method" == "PUT" && -n "$data" ]]; then + curl_args+=(-H "Content-Type: application/json" -d "$data") + fi + + local response=$(curl "${curl_args[@]}" -X "$method" "$GINXSOM_URL$endpoint") + local http_code="${response: -3}" + local body="${response%???}" + + if [[ "$http_code" =~ ^2 ]]; then + log_success "$method $endpoint - HTTP $http_code" + if [[ -n "$body" ]]; then + echo "$body" | jq . 2>/dev/null || echo "$body" + fi + else + log_error "$method $endpoint - HTTP $http_code" + echo "$body" | jq . 2>/dev/null || echo "$body" + fi + + return $([[ "$http_code" =~ ^2 ]]) +} + +test_health_endpoint() { + log_info "=== Testing Health Endpoint (no auth required) ===" + local response=$(curl -s -w "%{http_code}" "$GINXSOM_URL/api/health") + local http_code="${response: -3}" + local body="${response%???}" + + if [[ "$http_code" =~ ^2 ]]; then + log_success "GET /api/health - HTTP $http_code" + echo "$body" | jq . + else + log_error "GET /api/health - HTTP $http_code" + echo "$body" + fi +} + +test_stats_endpoint() { + log_info "=== Testing Statistics Endpoint ===" + send_admin_request "GET" "/api/stats" +} + +test_config_endpoints() { + log_info "=== Testing Configuration Endpoints ===" + + # Get current config + send_admin_request "GET" "/api/config" + + # Update config + local config_update='{ + "max_file_size": "209715200", + "require_auth": "true", + "nip94_enabled": "true" + }' + + send_admin_request "PUT" "/api/config" "$config_update" + + # Get config again to verify + send_admin_request "GET" "/api/config" +} + +test_files_endpoint() { + log_info "=== Testing Files Endpoint ===" + send_admin_request "GET" "/api/files?limit=10&offset=0" +} + +configure_server_admin() { + log_warning "=== Server Configuration Required ===" + log_warning "Add the following to your ginxsom database:" + log_warning "" + log_warning "sqlite3 db/ginxsom.db << EOF" + log_warning "INSERT OR REPLACE INTO server_config (key, value, description) VALUES" + log_warning " ('admin_pubkey', '$ADMIN_PUBKEY', 'Admin authorized pubkey')," + log_warning " ('admin_enabled', 'true', 'Enable admin interface');" + log_warning "EOF" + log_warning "" + log_warning "Then restart ginxsom server." +} + +main() { + echo "=== Ginxsom Admin API Test Suite ===" + echo "" + + check_dependencies + generate_admin_keys + configure_server_admin + + echo "" + read -p "Press Enter after configuring the server..." + echo "" + + # Test endpoints + test_health_endpoint + echo "" + test_stats_endpoint + echo "" + test_config_endpoints + echo "" + test_files_endpoint + echo "" + + log_success "Admin API testing complete!" + log_info "Admin pubkey for server config: $ADMIN_PUBKEY" +} + +# Allow sourcing for individual function testing +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi +``` + +### Usage Examples + +**1. Run complete test suite:** +```bash +chmod +x tests/admin_test.sh +./tests/admin_test.sh +``` + +**2. Use existing admin key:** +```bash +export ADMIN_PRIVKEY="your_existing_admin_private_key_here" +./tests/admin_test.sh +``` + +**3. Manual testing with nak and curl:** + +```bash +# Generate admin event +ADMIN_PRIVKEY="your_private_key" +EVENT=$(nak event -k 24242 -c "admin_request" \ + --tag t="GET" \ + --tag expiration="$(date -d '+1 hour' +%s)" \ + --sec "$ADMIN_PRIVKEY") + +# Send authenticated request +AUTH_HEADER="Nostr $(echo "$EVENT" | base64 -w 0)" +curl -H "Authorization: $AUTH_HEADER" http://localhost:9001/api/stats +``` + +**4. Configure server for admin access:** +```bash +# Add admin pubkey to database +ADMIN_PUBKEY="your_admin_public_key" +sqlite3 db/ginxsom.db << EOF +INSERT OR REPLACE INTO server_config (key, value, description) VALUES + ('admin_pubkey', '$ADMIN_PUBKEY', 'Admin authorized pubkey'), + ('admin_enabled', 'true', 'Enable admin interface'); +EOF +``` + +### Benefits of API-First Approach + +- **Immediate testing capability** with command-line tools +- **Foundation for Nostr relay integration** in the future +- **Proper authentication testing** using real Nostr events +- **Easy debugging** with verbose curl output +- **Reusable components** for future web interface +- **Command-line administration** without web dependencies + +This specification provides complete implementation details focused on API-first development with robust command-line testing tools. \ No newline at end of file diff --git a/admin_specification.md b/admin_specification.md new file mode 100644 index 0000000..b774c0c --- /dev/null +++ b/admin_specification.md @@ -0,0 +1,387 @@ +# Ginxsom Admin System - Comprehensive Specification + +## Overview + +The Ginxsom admin system provides both programmatic (API-based) and interactive (web-based) administration capabilities for the Ginxsom Blossom server. The system is designed around Nostr-based authentication and supports multiple administration workflows including first-run setup, ongoing configuration management, and operational monitoring. + +## Architecture Components + +### 1. Configuration System +- **File-based configuration**: Signed Nostr events stored as JSON files following XDG Base Directory specification +- **Database configuration**: Key-value pairs stored in SQLite for runtime configuration +- **Interactive setup**: Command-line wizard for initial server configuration +- **Manual setup**: Scripts for generating signed configuration events + +### 2. Authentication & Authorization +- **Nostr-based auth**: All admin operations require valid Nostr event signatures +- **Admin pubkey verification**: Only configured admin public keys can perform admin operations +- **Event validation**: Full cryptographic verification of Nostr events including structure, signature, and expiration +- **Method-specific authorization**: Different event types for different operations (upload, admin, delete, etc.) + +### 3. API System +- **RESTful endpoints**: `/api/*` routes for programmatic administration +- **Command-line testing**: Complete test suite using `nak` and `curl` +- **JSON responses**: Structured data for all admin operations +- **CORS support**: Cross-origin requests for web admin interface + +### 4. Web Interface (Future) +- **Single-page application**: Self-contained HTML file with inline CSS/JS +- **Real-time monitoring**: Statistics and system health dashboards +- **Configuration management**: GUI for server settings +- **File management**: Browse and manage uploaded blobs + +## Configuration System Architecture + +### File-based Configuration (Priority 1) + +**Location**: Follows XDG Base Directory Specification +- `$XDG_CONFIG_HOME/ginxsom/ginxsom_config_event.json` +- Falls back to `$HOME/.config/ginxsom/ginxsom_config_event.json` + +**Format**: Signed Nostr event containing server configuration +```json +{ + "kind": 33333, + "created_at": 1704067200, + "tags": [ + ["server_privkey", "server_private_key_hex"], + ["cdn_origin", "https://cdn.example.com"], + ["max_file_size", "104857600"], + ["nip94_enabled", "true"] + ], + "content": "Ginxsom server configuration", + "pubkey": "admin_public_key_hex", + "id": "event_id_hash", + "sig": "event_signature" +} +``` + +**Loading Process**: +1. Check for file-based config at XDG location +2. Validate Nostr event structure and signature +3. Extract configuration from event tags +4. Apply settings to server (database storage) +5. Fall back to database-only config if file missing/invalid + +### Database Configuration (Priority 2) + +**Table**: `server_config` +```sql +CREATE TABLE server_config ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +**Key Configuration Items**: +- `admin_pubkey`: Authorized admin public key +- `admin_enabled`: Enable/disable admin interface +- `cdn_origin`: Base URL for blob access +- `max_file_size`: Maximum upload size in bytes +- `nip94_enabled`: Enable NIP-94 metadata emission +- `auth_rules_enabled`: Enable authentication rules system + +### Setup Workflows + +#### Interactive Setup (Command Line) +```bash +# First-run detection +if [[ ! -f "$XDG_CONFIG_HOME/ginxsom/ginxsom_config_event.json" ]]; then + echo "=== Ginxsom First-Time Setup Required ===" + echo "1. Run interactive setup wizard" + echo "2. Exit and create config manually" + read -p "Choice (1/2): " choice + + if [[ "$choice" == "1" ]]; then + ./scripts/setup.sh + else + echo "Manual setup: Run ./scripts/generate_config.sh" + exit 1 + fi +fi +``` + +#### Manual Setup (Script-based) +```bash +# Generate configuration event +./scripts/generate_config.sh --admin-key \ + --server-key \ + --cdn-origin "https://cdn.example.com" \ + --output "$XDG_CONFIG_HOME/ginxsom/ginxsom_config_event.json" +``` + +### C Implementation Functions + +#### Configuration Loading +```c +// Get XDG-compliant config file path +int get_config_file_path(char* path, size_t path_size); + +// Load and validate config event from file +int load_server_config(const char* config_path); + +// Extract config from validated event and apply to server +int apply_config_from_event(cJSON* event); + +// Interactive setup runner for first-run +int run_interactive_setup(const char* config_path); +``` + +#### Security Features +- Server private key stored only in memory (never in database) +- Config file must be signed Nostr event +- Full cryptographic validation of config events +- Admin pubkey verification for all operations + +## Admin API Specification + +### Authentication Model + +All admin API endpoints (except `/api/health`) require Nostr authentication: + +**Authorization Header Format**: +``` +Authorization: Nostr +``` + +**Required Event Structure**: +```json +{ + "kind": 24242, + "created_at": 1704067200, + "tags": [ + ["t", "GET"], + ["expiration", "1704070800"] + ], + "content": "admin_request", + "pubkey": "admin_public_key", + "id": "event_id", + "sig": "event_signature" +} +``` + +### API Endpoints + +#### GET /api/health +**Purpose**: System health check (no authentication required) +**Response**: +```json +{ + "status": "success", + "data": { + "database": "connected", + "blob_directory": "accessible", + "server_time": 1704067200, + "uptime": 3600, + "disk_usage": { + "total_bytes": 1073741824, + "used_bytes": 536870912, + "available_bytes": 536870912, + "usage_percent": 50.0 + } + } +} +``` + +#### GET /api/stats +**Purpose**: Server statistics and metrics +**Authentication**: Required (admin pubkey) +**Response**: +```json +{ + "status": "success", + "data": { + "total_files": 1234, + "total_bytes": 104857600, + "total_size_mb": 100.0, + "unique_uploaders": 56, + "first_upload": 1693929600, + "last_upload": 1704067200, + "avg_file_size": 85049, + "file_types": { + "image/png": 45, + "image/jpeg": 32, + "application/pdf": 12, + "other": 8 + } + } +} +``` + +#### GET /api/config +**Purpose**: Retrieve current server configuration +**Authentication**: Required (admin pubkey) +**Response**: +```json +{ + "status": "success", + "data": { + "cdn_origin": "http://localhost:9001", + "max_file_size": "104857600", + "nip94_enabled": "true", + "auth_rules_enabled": "false", + "auth_cache_ttl": "300" + } +} +``` + +#### PUT /api/config +**Purpose**: Update server configuration +**Authentication**: Required (admin pubkey) +**Request Body**: +```json +{ + "max_file_size": "209715200", + "nip94_enabled": "true", + "cdn_origin": "https://cdn.example.com" +} +``` +**Response**: +```json +{ + "status": "success", + "message": "Configuration updated successfully", + "updated_keys": ["max_file_size", "cdn_origin"] +} +``` + +#### GET /api/files +**Purpose**: List recent files with pagination +**Authentication**: Required (admin pubkey) +**Parameters**: +- `limit` (default: 50): Number of files to return +- `offset` (default: 0): Pagination offset +**Response**: +```json +{ + "status": "success", + "data": { + "files": [ + { + "sha256": "b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553", + "size": 184292, + "type": "application/pdf", + "uploaded_at": 1725105921, + "uploader_pubkey": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "filename": "document.pdf", + "url": "http://localhost:9001/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf" + } + ], + "total": 1234, + "limit": 50, + "offset": 0 + } +} +``` + +## Implementation Status + +### ✅ Completed Components +1. **Database-based configuration loading** - Implemented in main.c +2. **Admin API authentication system** - Implemented in admin_api.c +3. **Nostr event validation** - Full cryptographic verification +4. **Admin pubkey verification** - Database-backed authorization +5. **Basic API endpoints** - Health, stats, config, files + +### ✅ Recently Completed Components +1. **File-based configuration system** - Fully implemented in main.c with XDG compliance +2. **Interactive setup wizard** - Complete shell script with guided setup process (`scripts/setup.sh`) +3. **Manual config generation** - Full-featured command-line config generator (`scripts/generate_config.sh`) +4. **Testing infrastructure** - Comprehensive admin API test suite (`scripts/test_admin.sh`) +5. **Documentation system** - Complete setup and usage documentation (`scripts/README.md`) + +### 📋 Planned Components +1. **Web admin interface** - Single-page HTML application +2. **Enhanced monitoring** - Real-time statistics dashboard +3. **Bulk operations** - Multi-file management APIs +4. **Configuration validation** - Advanced config checking +5. **Audit logging** - Admin action tracking + +## Setup Instructions + +### 1. Enable Admin Interface +```bash +# Configure admin pubkey and enable interface +sqlite3 db/ginxsom.db << EOF +INSERT OR REPLACE INTO server_config (key, value, description) VALUES + ('admin_pubkey', 'your_admin_public_key_here', 'Authorized admin public key'), + ('admin_enabled', 'true', 'Enable admin interface'); +EOF +``` + +### 2. Test API Access +```bash +# Generate admin authentication event +ADMIN_PRIVKEY="your_admin_private_key" +EVENT=$(nak event -k 24242 -c "admin_request" \ + --tag t="GET" \ + --tag expiration="$(date -d '+1 hour' +%s)" \ + --sec "$ADMIN_PRIVKEY") + +# Test admin API +AUTH_HEADER="Nostr $(echo "$EVENT" | base64 -w 0)" +curl -H "Authorization: $AUTH_HEADER" http://localhost:9001/api/stats +``` + +### 3. Configure File-based Setup (Future) +```bash +# Create XDG config directory +mkdir -p "$XDG_CONFIG_HOME/ginxsom" + +# Generate signed config event +./scripts/generate_config.sh \ + --admin-key "your_admin_pubkey" \ + --server-key "generated_server_privkey" \ + --output "$XDG_CONFIG_HOME/ginxsom/ginxsom_config_event.json" +``` + +## Security Considerations + +### Authentication Security +- **Event expiration**: All admin events must include expiration timestamps +- **Signature validation**: Full secp256k1 cryptographic verification +- **Replay protection**: Event IDs tracked to prevent reuse +- **Admin key rotation**: Support for updating admin pubkeys + +### Configuration Security +- **File permissions**: Config files should be readable only by server user +- **Private key handling**: Server private keys never stored in database +- **Config validation**: All configuration changes validated before application +- **Backup verification**: Config events cryptographically verifiable + +### Operational Security +- **Access logging**: All admin operations logged with timestamps +- **Rate limiting**: API endpoints protected against abuse +- **Input validation**: All user input sanitized and validated +- **Database security**: Prepared statements prevent SQL injection + +## Future Enhancements + +### 1. Web Admin Interface +- Self-contained HTML file with inline CSS/JavaScript +- Real-time monitoring dashboards +- Visual configuration management +- File upload/management interface + +### 2. Advanced Monitoring +- Performance metrics collection +- Alert system for critical events +- Historical data trending +- Resource usage tracking + +### 3. Multi-admin Support +- Multiple authorized admin pubkeys +- Role-based permissions (read-only vs full admin) +- Admin action audit trails +- Delegation capabilities + +### 4. Integration Features +- Nostr relay integration for admin events +- Webhook notifications for admin actions +- External authentication providers +- API key management for programmatic access + +This specification represents the current understanding and planned development of the Ginxsom admin system, focusing on security, usability, and maintainability. diff --git a/build/admin_api.o b/build/admin_api.o new file mode 100644 index 0000000000000000000000000000000000000000..c77ff7366d26e6e3406cc6a934bda7fc651e562b GIT binary patch literal 30056 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 z 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 literal 202208 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

)Wyj$x^DCa+Y z>x#{47>qD}BWYz38;}&a|L#+ucT|quE&2- zCq);FHxElvsyDo?iZ6N2&w*shn(Ga^EOZ!;OP$46$~ak}qS;nu&=+r@R*sy$?I zb5=D-n(=#^(S2Nop{hb1!?;fhU$S_KwaGqcI;;Du=(tO=R^IaCqQf{*9i(=|HYt}B z&-Zz*{-4~KOSwc+ZhhRUfAfB24{MiyS-bSi?{b;!(sEU&`j?d+<4CFG7yYU-WCc3D z+SpUlGYf3>_<=Z(3upUS-RP$FFc;x!uL!7JHss@&w^~~_Ser7PpQ z3?hYk?JLbUDcPbmkGpagjM_*)x9=1Z4TA|y`szRPjPs*Xj^?Rkvl4#UQ=2SD$NTE9FB^He`Q-RHC(`XIp#hY;B%ph5nF|3~M*4Eu1eXttGo{5|6!HXf{U0NwW77t!63T7B}By z%B?Hbj(%3Q7|*F1kz-h~9r?i(jifDINQ*_a%Mf?^SO2va+Q(_FK;d{c2KjNMru-s^=dgqo=s)Irn7D)oR(kxT;VcBi|h=`b$@EiKunt+`KYT{IZ<7?XB+_ z&q_6+eu~e;V^4Fu)8i8h zs7O7h(5S3CIZ{ z@%}YlhRuk1Bzg8JM@p*-my z_$N84Gxv)`)coWJ<5QVfms+=d=c!Z=wYHv77H>C>lkZ~ArCMb+;;g<=l8jv?PsXby zH?rXeYcQ*m6s$5tJm!kNvH1=Wb>&i*gb|XV{P?3Uo-H$;EzausQtdMDcUE;%rI9(* zF54v21Gn&yL99#=k=c}OsVla*L3EiSHI*dS}{%KsbX?IKaP(4 zB&__LVard|LOHT=z7kdH_0Fo0fO_szBz>nkWu6nxv62~iIMJu&WZ5YFLUlWrx!qZ< z7PImPySp-1cxTlBbAw!mjIVF+sDLVmuCRiP7?m?3t7u+FQOh(~v^TA9a4`9@=+4{p68K{;7*~YM7^= zv+7~lO-?R(QlrP$y{OJuRoElsX`*@NMdXHtP9?X4{d6KK1`d3&-S6?q(AlER}X4a|N zGROR?s;Xt0{AK+9xmFESH0+`?bAQ zmQ3mYg+zL{Bmbt#{NZQW+3cUG$Oke8T)$Z=$$ENpot5EK>q|zv9M5*O&CTCrFqP@5 zUj4pIT^&soebgY^tK~fNdM~54Z4c}EyyN&AWx_Kf@n4UBo=n33JMm?C>%SF$p1LvZ zB0q0LoKnc>&Mkdqt;ihb%&Sug&Ifq{nw>PEEdG{Uk&1elvz$?Q{`hjCS3s_ohi{S( z^?-Pm+`$B7X=;DzIr5bFU8zU;_*g1QK7N$}__Sr3+QnJ2=5!ea%#L=(az*k~POj{i zej+U=9Y?kq%bV3%zDd5yRbOs2j4K{g7l(?G>PE*SJxoS;qx2q4TRp~aUa?eWI<8ng zImJ|d)tHdK@2@HhsKd)sXn8B9HqdS?lY^*JRPpMP?(JIZ^TyYoq8`liL0;iem$JW~ z0qUxDm+I(pRg+Frb%T7D>zesIw&NW7e1mx;+2AnV{8-Kic_d#Yc`=X6XXXRK)|qLT&x5V| zUbU~e{-<_-+jhN?^G?pEs*iABGavEp#{4|U1zB5TK5tbA#PVfCRjM_H^)uHa%}Mns zlKAS%jz_x1)&l66QWh)oeI_lH^>?5;m$-ty)P*q!(qZ zRQEh%B&Kn&87DH_%5=F|6`gONepGv_%Pm&Va_yn^_(oI#LfP#hR+}5TZ=9`(RgXh zkPYUzYSn?6lCeU?{ylenRr!{FaE~ghHQV_hx5%waL|L?GZ8DyeY}kfJX+LW&IKFmf z59`rES$wTLGuV#XrWww;JufxiJd=fzviKWnuIMh4c|+&9u5}|-TDxdZRUxw3TUuM} zC|$EiRgn6la#Z<^ynIqp=#i`Ppx+j)kVjeCik9wjLsabCt+H>6R1*tOU!{RJ)|G$aw8bx$%*Ni&ST; zkg-RGpmNz;Z5+L;EG0W{oFb~u-hg?`viNH2ttr(!9(iHL9PuWa*;QpfL0&^K^g-EL z^-7w&dbB#;w8H2fU-6T^M6Z;&q~@&J-%L=xo4Jse(n<<_MvF|#R7QMq89zhrO;sk2 zomg0OMJbLQDfIrw1D~i4EVbdIvnm8RO*QqxN%2EAD3N zLC)*@shXCKYPJ6{s;jF9#MBR5MnCjHD+MX?Nb6*9USBEMP${T$YJ%*lk~mpDtx?{e zo2M@PRK~n1ADG*#2jdWaOf>b};JvFXTpBaqw^A9Vx`T8Mzi~%eMmqByT3HXa>dE-i zUQffeNloRe$&j1sd^N3?{EZ#fZm(yDoN4MERLQo=t$JtDtTk1K)dkW3ErZnkNvE}vHW-RPIYU4s-C4AS4gns4oS`o%Jp;9T6L{l zQJuQ-i;gGtvdl3owPvi!pJqPAmFe?AGK0XVyQVsKoiirCBV*LV#);Blt^T9tr{k;N zko%LHvtm1(-`mz>?6%r`*gxqoSVZCQM!y8SuB_)1A1tdg17KfaX1%I)!;^VO&M zfT&o7ACsGFi84anK)fQ~F%2CkFX4axc7e6LtG2CpTPCADKT!{=TX)XQ&xUPLM>;Vi zW8V}xNsZ5>!fi7eYkf{$=$8qCx~5d!DtpV#XzX}7%%9Gx1*)j(;kBAmHcQVqOb%k+ zoja>%$~JY}mbaukjMgNU^V)IBDqnZJsD7d;`*f3iRGjDKu-0x>>I(IuTy)gtEmGBL z^X(m*bc-*1u)%SFpLfWAhrdxtF#1L2Ob^*GO#o_Dy6bvR`h~!PX|J zcjIl@TAeC(-nL3++w#)_^_H+&S2fox^3XBg&8_c9*H#nKKja>){1b_sfS$bafm0ptQ8Pym*?Z zdKBrbGF!JcRFMBQ!SXjclHA=&a=f~4++TK)Q$X5i%qP~tpOe(eY6njI$XX0lmw79~ zUWjmu6=5_Y49-UwCZe)GPa>#G$!_^AKcaJ$Te}{NUE9n8sDq~v;b<#@6A|9cM_7gk z|F9xFvd+rgllcg7MEFJ8Q?lQT2=XKr*}n!6KC~jNFe605R@tUt;|tctr>u<=)VhpZ z|082cEC1|u$!JM;v~1Cn@xUqL`tH2X&icsik)bmd9oya<@hv)bTDs+7t_?ZAGrbN+qu-1`o(%etuyfB&JpJn>SidMUq3V#|DP(Pcf3o?6i; z+qTKJ*46p(NGfdf{!Q}2RZODE6wsXAKw1Xrr!+(sE5}%wE#6L0vZ_oJ9Ca3CAw$ zF+_GbO!DHaK3w)OA9i%~Zmw8?Ox~iUwcdVn>*N4;vVu1N{YOy>{Xpi(XHotD>d~(a@^2MCz$T_L% z3BNxue|sS3ojHo;7u#NQq$_$#0u{TPOVtkRVj_F0`EL9dBX*G*|J53Pt5SE|$&aVs^`1vuz})eE#EE^Tqtq_=i&NwTs&Cmy^tSqhv_N*?tQ@kUrObqtKOy z)Lt8wNk(PXxj>dd`Yn~$SM_NVhT$f}=3?LSL-B)8YUuReO-raqiw(=8{Mb)yO} z*viLAD&5-Ao1`)%AEP8><>OY_LtwB8DjzBdfqev8ug#rra)dp^TUyPjnO_~T`8^4* z?+CxA!f#RdrH-(s_J6!1yjaD*8)0ibPu@8*Utvti(3U$9))@)aYimchd}HS8YAdvl zmCJ|~8YeqQo@ZDg%_?KL75c>5?<5IXCCDceR zFZr%A^JS$`JgVm<8|BRwtb^vxU$cXoXpMKOe$+~veF3JVP)>byVxNFjwqx-6+h$|D zYV5^(7V7ctv17D~1LWMivcEpMK=be9yeH*cnyM|)+FSc{NVcRuOJk zs=h3eliRD(>pv@XSXJYJR+q+Z^yI7OCaE@lU7ailR3yPSdwfruvq**4p@-v%^x=hREKz@RumdX#^r24Dev~7?=j$S6u)|1QC%f#xmD9^aA_mBoLQnk$iM z!nbn6S`y!&x{CQ@*?&t!L;c>=eC%tMA^&pQNR>r->?hbrCueV7uDv+;+C9;>He$U(Tv($wQO4jv7#=&B9|97^c9cail>Rg)1Zzb^Yp z%@(R2YX4#6eX2g>Nn`#IwVG$jvQ7ToyWFzjmm4G>!*9uLTAHRAuF?M=X=Dzb+EbVwj5c*7Em zxCJICC~Bf+B$`MX66waEaR5aTMNwp28R!NaMH)L1?zHXTjQcXKfTN#;Jn}a|DHe3L;BXK?bNALr%s(ZRh0w7W1(>N(Izwq zlDv;sfiPUAo*yPjo+fE>)+Uqu!z2lRLF@`JlZoNYWbA`(>>^^A1QOk)QBXe~;a~_`z{CZb^^2V*uTWkoN zE8dCBdLF@)!i1b_03dw$lKSR5W273yY0zFvZB_F{kf{lKp?p=wGiOi@lSr60s92K* zje4`W`LatH!8j#geUl=SDgbhJ<`StDT??;er$6kN!Z2#D64mWGa}R~_LB({nBQz*z zzsH7s=xiLAg%3{GMttThV9Y8?;K)dnt^A+i3|2&iwIewP4Jn(IK8XD2l9ol98Q-bi z5uW}^`@x_T*YzW(oQ19tI!Ws76Ij@?fIlnTBQ_-4T5hMuhIHr0r+O*pzdbs8zsldj z*o$@W9_VP@HJ>jI^ot@^{jz?q&|~0F@m*24e*DaV&1W-?V`|Yj*?_kX>?~o;sm43YW zUnHMPpU{y|yW5m5LT&t4#6CRf!(Y;;RZyVxxSO?dUkRRs0+bUm82u*lV% z1rj%>c+p#sKVX06ygU~^Wn+CBu-6pE`q_n*1AEVf)fiYP4--zeDLpw>!i2fU&xIGV zg=($>Nvy;!IR-_euz|^FUbe_!ZSV=Tfjp*c0%j65t(~Qh4dq#l-cdFOTEUGt|6HDV6cJ@}Go~uIt|>+2Sa|`+@>%eujHwLt*JC;V5%q4$1_T6kS3$OHu1C zlPI-nsh{HaH>pvz=E9D;vp~v3Z@EII*?E--L+1E;|$O-z=sX6Z=GiUSb?S2nIuaI=?y%FkHJPHk@Lznbjjtg zRg1ih=d5HI@8K!m;BuEK>_~&>G!yD-@Vs6_MK9JXN()Vfkp}pZ0lsN~Wd^7=@px&( zV0JPyBC=@2{X$=)EgnVGLW1#}$26!qB2e{n;rx~ChVB!pBe_azn0OuIs3jh=6 zqo_$coumJujr{xDsf~PrkaLx(W*eq>P#A7Po8bUL6{e^()5wQO4zR!gcbiHdFu_d~} z6hht_PwWu!oHL~Q8vVB6QXXu?hS%h@Q&X#v*y%(B;+Nyc6r0Go zInMR1bGA=4f)^rjcwnGtxS7tDmt=lvcE016mW+Lq0>y5wBO^q}>2I)q+gO#4Gog8q zk+x(T3vTdfM*#>pI{pJkpfGvHAxl|VxFBZ)gwMw!Msd| zfYc477SdbgE1kbEA13CZETOCBS1rt=En-u)>y4mqnka3%Fe?~4G8muA?8IeYkL5o} ziO%cLjQNKd+p&M-6D>*?Gi0x-6gN3tLWs2{^Cc?O<&E>i%i5N|2rF?wvkEF*aivaY z$G4N3E}^D@*rs&i5fpNr+l75tE9Xiu+c`x@gi#|qSbRyARc#xDow<~h?1bO`-E_iL z{Lm1xZ&^kb);43`kzUAi`YUCo z8p3`rPR9ep7pVlZ*Z}Gh_p%cCb+&I8YL&h+R~uZT3|!g+^EJ(%YC>ITKB3=A2n!PE zl?p`wUF_BZq{IySa!I6S(W6X~Gu&K#Nz>P)$?4`$A_}dZ$8o-y* zM;Qd>o6vBBfV2;Ja;&MV*>v{<2H4F2_Zi?!1N?(*r#*npl)C;Zb-5~?e1rRFQ~D3@ zYiO7WZK5FJ2AUGC&@L(cv&nFh0UpiZ1IIZAm}7va8sJDnfizRteJ05^L+?#+L&{|6 ztVeZE{EQWo!X7l$E+?gkE#W_VLpkH1e>tYVzQ!852XU47D>Bb{@@khA2Fj8~gqdi? zLxrrccb4AjSo14g?i~BcbFfJnTqgW5%k;pq%>6*qoZNB&H#r;E8>RnoNrwLW?-pXo zHv~CV+fcd)yC$JL6IuXy2zAv^Q6Y>=Q^uPN`?ZgYw#cqbymXuaItJ)ta02XW$n!Bk zoo22jk+YN#<3jk8XoUZ(kW@r|qE;l8H_07`wa8nR3}yNi(cn#T>ZU6M>G(T_l9;DO z-S4tnDD6!a*rk(`Mg8^EfTV`(Upjs0IvHdONuFffm47viyPRJz8}+(NA4U6OLe3G| zveGBsRf?pU&?83NZu^_$DypFWY4v#|hiG3I;1z~GgAH)G0nRkQEJL(^DX?^iN%B`h zDO?0Ldu8C<^BuhLyo~yI=bcKpoc6#M$s|hM-6hTS-;z}Uv9@}9^{}5{s~12!xxO&# z@5*@T#UjOIb-f!5{1>y{9twoDq8R3IL_%0$N1XXXphFuA;s5}&bUW43qoJ-Mq@EKB z>ZPM_5}4(5`d16D$u}A5pOa>k4#x&c=yemiK(MHFp3_j#d%_ghHeOTUy#~0<0CyVT z?FQJ{(DE%)^mPEW({_`{xm1!s3qdqx4Ks)}w^_q*8w&Z*Nu_PHn5MR9riq?3tYv=~ zKieQid)d=g`;EC{af`^mste*E-2PZKjW$BS6CwOY%(%mk5P$sxao%xh7Oa z(};UsLq#8G2bb>qrvm?MfXfYVw*lT^fM+2{^?)$vz2l{ClEldnc5+Xy9*Ob-cf2ZD z?FKnS^T0F2vS98Oh)-+p9h2xq7V$-i7sWMYQ{j5&rb8v%B?M)ikBvy^`>+O$Ci*B- zV&OaL)?AcHDTS+?o~xy$o$n*5+&7^|M)#nw8VBh-RSPI;)XNlgE=9>GjuI~yKHM{2 zrdV97|9B4^u8i5rtoR@{(@zQe}w9lU~p<*K;?$uDy1&mLUd`oh8<#q$yXMkTD;B^N0sgRtDI$zb4 zzcGbku#g$u{w?5>8yk z#3HK<+OMj6;x^{k!u1S?#OZ2|y(M$Q>ehIBVGmy~?BVN$J$${e;455v&wLk=)fD^- z%<8qJn@bC?E!$mOq%Hr2xk_Lq88OeuA_*Sst7{V&5kbWs5$yNKQ%tA~8o}s}&YDKn zhr^WzORxC5Fo#<1odQzAFLg_lUwWI+ONL*%6G{wV5T@)8_T_*%D}VygG4dsKvi<0fPRYQ znfX8x26Cieq>F{`4WPXyK?l=W#sTPTryiOAI`|_c`Dx*S<|#*(&NOAdMxw-D0qMMA+&G>#Q4bQ;_zESyV#2eM<-P5aJwYDLOIv2fh&|HU=3qBo#BFwP~m5NVjChKQL=YMrq<&2 z^J+*}KhUc#_WVFMIbpuXTc>80Qs3_n5A{mwXP_Njl6A0OtJ*q>ZlQdqm~t6#GM|4W zCijO;xl6fRe#y!1V|vM567GyI@i@>%)JevKgvSk5>rB>xj^FKK}o#X z6mD=5BNgxW8~=q}U5)Yex*Mw3@2WoO-47!BQ@=tw$ex_Iksrx7RGH5?NBdJ}CgpnD z1^adMbN`H%ZB=iUFxv&91AHala=dB{9~Ff=jNZjE=T$fH^T5qrTwDAes+PIG3v|zR zQ{71_(M^XEmez-5Ghw3Qf4yta+2FjKEFeU`H8}f6Xj_Alu?rd9{^NXigX7aZme47| zamyBzY2n}bxsnX~P@wLs_5ojAr7sb%KXi6K!iv4D49O|$u-MV%#XsNCO&U~lRx{%| zOX0L@G*?P_UN_19_x4LWBnDxM{{??Ty_0)~x$(=rh_>UiXz_9}H>{VJvQ3uacJk3< zGIg-e-Nx+%UAV>V#PtEvDF{nZZQ(m+pNTmF$0X-%EhB6WPP*z(@=m<1Byt{oRB8$T zMkG+m!D4e8l$?;*19`kiT$WTqsXTHvdNIy;s?jv&!mcIGQWucr-1aact{6f(A5oKg z|Cugi3g8RbNM#4{_XrL=xWt+wOt57n{V9u?UvP(TPFSl#?R>I13n&(Tv8pd+%&YIY zS{47J?Xs%h6>7zE`aFj~yI=Xc^klvfT3>+~f@zYm9er#b( zj21;MQM^WEUDEzuI*_!^5~01jA^55`tLRtI)4BXXmm1=W2I~9%3h=>{#E)hqw@sZ7 z74Geso`@)&^^=l|J=4h#$ZA6_5wWUsvZSoe6PXZF_e|$VyVx_Wo|-P$?{rQh542tc zZ?|mdyiLdnKO`}w?TroTIur7lDdAEL6=i@vNnR#7ol|arOX(bdFB;%!2DrumZ=q?V z>La&<8 zPVL1ioG(`n2;ToQbNOO*#rF$?nC%02L*R1JU+xg7oGe`2PWmV){V~q9M+swe% zo+CMAD?Ox}?%@keNr4PgQv8$|kk2`{zzmEYe^x%a_X#D*g!?tL+t^YrGohbg8OT*> zLZ{Se=v+eO>h1BUq2?(Pt(xvL25^i?|Iep1{Sg}dy=TsrS#j^jPofzalwpnr48%HCs?$ z8sIns3;|4>wokY$lEy!(jF7+^hw@Dczc!ULyrH4KCRFc^e^bz%X2{)aGGrRy0dNG5 z8w_x(_CnEbF<&O$+H1z4^XRFjy_4_N_O|mXSCQf)`y0R{|kivdnH`4%WD49b6jg>wZ!t!OKWoGM9ja96E`RXy3&l`<0p75C~i`6ps` z-W)|eKp8D!_dF@_fOcZ(ALlCFwwll%p4HIT8Y+66kp+ozkRz5!O!I)G;w;5-Ag z5xa@%J=DbjxaJg7cZsR{y}zW^y@HT)r>U)*sXJssN0_?Dn-VWJ)hr-6bzfwF!wv8a z11vH5qKXM*dkG|*69FpO=1WCKNs@!f_B12jBioUbnaBcl=cWDz@9IA)&AL-S3(dMp zaVwM&N=N@&k$TI7E;iEW6%7@wqZN=R-Jtle0e)hD&5vuoY6I*8x};oUQr=<;YiB}N z5mE%rWCZ8y3^sHnGHxg%LUsyDhmzlQ*?E?3*K3S+I?w$qpH0q}C!5yHy+>M8TKK5e zon@+d7@HGh-J?BJdLR8v=m!&e-%P|`5;F4T9MduHOSG=O2OGd!CjCXGRj+6$X95jK zJSgbIJ7>etpng0*o|*d;zMbd?o|&8XpU8c14^>Y^%sOv^Y@7AjFZ!E3pFwlueKye< zD2UM$w_fy{e(2sYYUCm<*2^;4KE|Gr+qHQ1lpm=C@^iOWBvw zASVM(^3=;K=vH02=-ze%qr3-nbY?Cxrn8^ZrMtPDoMf(#+)k{bP8X(RnaXADFHRFr z70UY0Xso@-LpdVAL=l*KhD5n0aEcLF{dqcNSH}pCx0-4xLTsILU<&!A$@+_dLw{@C z-<9s~M)!BC`zxnMBwx1so9F(D^-bVq?r)X*JJ$W3=>A6dbuIuW_qu_u0ckRYo9kpp z+$|=qoz4$v=`cN-3M~`{ti7Ln&p~e?#dz9MsV8^1rX7+lK-(nSZkccB3+I)%N%w`& zfCZnd80SkGPpx-4kwRmfKT#&8FsnK{(@pF&Nw>-c@Xy7BOLF7_%!T)XxH9x7gq*v< z6ReJfY;epSCX{Og-b4)*eg32(zJ%l?8Et^84Y1At&ojV*;7nn6J*+7UO<_w-l3x*; zFLj$i0XC~h=5%^jP&{Zry@>wr4x*Of!wyN#D-Y5H*U;6?l>Cl1uJkfvSbfukMj8S5 zvWALYpehjanG6pZ;Ag^YNCZlxT~PykcAf$ko0K=3!qQD>EFssKNyfE^B8HC>T)_k| zKsqI!W2K-MG6kPx%6Jn?D%BigLUWC9IYL84hZ*#)HW_yRL5eCm-PruDG{A2ZSbDNz zLagk}IUfSluDpswPMstZLX<-EO<0e+=U1Uc6{&_c;~_y)Ndrjl%f>hDbG zaD(bN4HaDp0ub#Dli>mbyc&K1___fW8{kQADDYN;f4(Vfo=MV!P-{w-QiRjI97=96 zhSH^s6R)FBGPyR|LYUIuv7QInRVH+!kzh+SRP?&cT=CLeli?o*STI+CIR^NM$@i6p zpyVK8of?2j$s++fQzgm4l+0mZd6c}GiV`_!C^E=9EZ$3!`a=hWW^MP+-8g4gg(8TXhnjyLEl55 zzCVHq^!$hE@G}gs!6fNJ$ZVp=bJmg02m$8^L2tkCqe!W=S&C;*vb1dDol400PGd@^ z8e`8I6Pg4Pv|)vYie?$cJkHejZv!kMNn*GRO%a|rMZYg_`F1Uoi=8)t$aKO?q|*uX z(doqfebxP~b$?g7zZ>1(t?sYvk?3^d{^q&A1@3Q|`&;Gyj&*-0y1x}*Sc||!RiIfS7tCX2|bZ%l=(&=B9~v zd3EjZ$j-Ha&ZWW`(%;$O;*TD1LeT+OaG|c@|d(gUV4hL3Eg8teGSV-Oz0?D zNB&6?(xv-AGa#-q(L2D8=;4Iib-Ozmr2iSJlq`|U0TcapBTv?gF|OuzOfrVzW0eXt z{76M9$4^NM|4f@yUQ`fr_8VmHF~))>6SB>K|4KteH<+n13Zi>Ol6#cvu zK9gdyo1#}TMY@+_gU%gAIfMldj#|4cw1QR(3)Q@BQWU#oe34A?xtHQmlS0B$#+%6$ z|MpVcYEq=RWjss@$TN#*Ivjr0FWzNJRB6J_kAn^gp86-kNc`GUmQSvI-94Yw`UZH+ z>7psePg%s!#DA!X3UvMSHwhnnUgx21%9so41f9}%z|OhA^!eZTmM!Fg5|r~gb48zY z_3N*x#B*7>nFO*WV-Dps4{UGEdQ-(h=?Lm{n&Ag|>cS}I7fB)UoNWvqgN-wgs>O!2 zX$JMUtKd62N~+G^{9ewqn0->w<})ZtZYtOPELk}}BYEPbPsg+oKSNU;6)zEXHVK7g zKBk2!tFAFxbZOmd)WCvEo8O`>CjLn-@h|`O9)_75?xy&SaV`D%H(^H@&(r--M2mmpkn?$iJVg;$-$UElZ7Us zC>gvB{p7sI6%^KT-eb!mZ@nm4bG2{3khJnM4yeg4&P#*XrLsa zdb7}xl$&lRSCXW=P%B=#{~yXzCm815Fk3^pCiKFq8ahlu2eU{7wcX|F z7M@-_N2;4fb(H;-DSL&fDQe1o%YY^u>l4Q@YYupINOZiGXN@AEhUlDN0J=e$VcgS zHK`@GGmW@-{(QHPP>v%yTi#-Zc@h0Exqm=AvMJDOhkS$UGWrsHE_q5r^GxVBj9}tk z)KJlSN+rnwTY>i*;86^DfO`!vVt}%~NS=G5zPmM1(b-CFQWsBQG`xNrm{smiv4fZT zN#}e{Lp`2~z^4q4L6RSl_dBwfsL*eDWvgRkPj8Mj>rkB7aYy|I?m1Plyfw1toN%Ya zxdg_~;XBmXdxV-q;VnEr&E~d<*V1Mxn0hQz!wI0I*GuAwBy`(BRcx~ymo|rU3B}SZ zrgJVd9%gfIupCLy!L`^qj{OH1xq1f$>GBk1}1^}o2ZSP!x=-@<_m=W>_k%`zo>A7;# zzzM%M*uJEt{a%X4mR_Tw8%^lgmo;>Sdu8xivo4pYc+s_!QwJ~yQ+7J1(?dFj69Xg-kd1}3#Wk27e?an;o&v|| zx=*I#ja0cTGdB!X6Y0bku1{=)mtf2(5Ql;C1Vs`Wb*F~zHK8&g%|W8M`o$fLp$}+# zi!E5>GyW=E^VQvoPg+?PInZ(Haq;r(m4WE)cDx7@t@G7w%t(oy@^yUDB9diJ%_bSo z#_nzxUdz!$_$of>35~Fbc%gA&lHc2&-}m+wRIXwLOV?@ooCIhNTV8_!B`jQ_dORg+`+4i z6~2!{E((<_$Ni>)#pZE#&bwk#yqsSeIkK|OOk|Vk+^N!ad)7xJj zekn4&Lpb~##bh}@!Sr^0N-0zC;Ze((UrT}WJJH0tjfeF~+h?V9Tp35tflD(0&fCfe z^Uotq8czf68s_`Z-WiNG`2uxY+DA7HjD8sL&z|A)M;u@HyN+8ceJdKRVEiKAypCj; zj}%XvBlPmU&-EXP_i2bmcJ?A0IZ8m{!;;}u8WwCcb7Q@Rvy$Q0By6Jo=3mAWRVN>V zq#Nwx>UMdlhly?^-HB1g_~X^nX08+SM|aJb+LhMMM@2KYxg;(UnKJW0GiK|lJq=vY zomZ>vg`jDP!`&o|doPL5VctefBB>J&O_EZYxv_edbd;8)57H(5iI~B0hvd)>H>>Oj z1p={4xY>!bPsJ-HWjFTXKJ$9`xGFnP{88xWNJ)>pDVyapn^6~`iZt&x5KWLSkc4~p z3F%FpR1mI=oyY|-;(^N}8bi22rZzl!J_SU${ABVaf!J*f!4YS{v@0i%jt$NudJs3_ z(UyTzS;zG68i)-ZAmiW)7KDh+=9R9i-yH$_2Cf|aRb{(zT*|pobX#yr3!F0~d+HPT zuZ`x0kBtgbe1pDyEfDyeNxgZ7H1>(cRjM!AP0hpmx@GsKQmAo;RLLDL&Z5cCGj>k% z@PbC$8|NAfGz=Cl;I=U7g0uwbWXh#{xBVE`B-(xEg*k?m)^*ujn|G^FQ~X8f*Usg?lPd9JmX`%6Pu_v6UKQdw)slAB+)yTE?uniG&*C%ZhrSsW%g?8{ z@S7=g2yzmQ@^*xd6HWhvkmzTj4PMHx8b5K{kwU2ybHeyJp1f2qS$n65%v_Nt)#wiL zvH`ju9{P>Pc>Gk-VQN7zx|ADd1|f**ebu$00>s=gQ+t#ghWHNr6xr>Yazx4TQ`R+p zF7!q&ed47hAL+v@5mFb#^G}j%*h+*ub*1 z-hvqhj^-CcMD}s;LZQ4qjkmYZx8(BBuV%M{79s&fQU>Bz3X+$W9B(~XSJK-_J)XZErd;HYm+dO)IW=emCa))Kbnjs$`ttDUQsA<|zRRUh zL#6QF1~1Dvx3>;>zK#&j&D@j)7kUH2DSO||qL+;2Zrx4IIg2L9 zvwzIMJQHE+5uLRkwFopSR}bqI!;Jr={NZ#Qy!*3Wu@(Jc6uo*~>Pu|p`2lVWitfv^ z{8ph;s%#5CVe02N)i>p_A$_-5*+UBCzQ6)SKe-i#Z0t@Q`-zuIoy(fMw8biCDf<#=lZO2DZ(61HZ^J}xD%-{!YY zS_62o^@qVMZLOM8{#M4Xp{RK0<4L-pCspwPxy%TV$Qc8D-G7vI*BO)oZsI>2 zNHKGLMHvul6xY@nMihBTd&{iq9!wklnfoG?7REn)1{gPo-g2higb?EHeeH+uoW7z@ ztI;m>WFR&F z7X#BOtNIzJ)0Q;L1O{mgT@uZ@$`NX)xK@+#Yd6nM}bXnTyK(rzosZx>a z6FGcYLIk~;2cpwOVl@Sh+7XB@Yp0>3)}s-oJL`721iDDe;HdE!l!hu_1vi#wEi3cU z)m7=s($hp{2Yh3)Wy0XWP4QoKjzc&KZF@+gV$;*4yLbj_Om@Jx!Ku3eppS>B$7Bb2 z4FT>!pLdTMLFlW!de>5jf58|~HH4#MP-<_=^(90>api(cSEl{IqIdjKV>)|sHKgi;rd5V516(Nr;?@nkdZ9AKi4r<%C*C*R1 zwAAxaLf+_lIP**AuAM|n=KTn)Woc)Z$13tGkQy1#v!meP((g-_Gm>mI$quSxW=kEh z>G_3hgi(Wa|6(B~olH&f$v)?3uc|+ZY!o_UDh+j5meyaUzgRe*G2dZX`e71E=|u=b zM_Scrvht2l^*-&vFxOOYe>+Ph#eSfoH7r-|P&=|v^^;{8*&PEhK%$qSwlo>S`G)Be zMKy~lMn$!hqt_2e^LElq|NP#)nIp^6MKWb2UL>K6Q{+>gj+wV%eV(t&&rb`E`$9-5 zjXT%F2}^F;NDJvshf*wExK;8_<4D7FWHEyysU6(8rp2`a@CMWjmh=4(H>1nCF#O|LWEA#3NV^MFz^@iu+y zi~~!N>my||+Dxd6gxQAc&A(i-m@P{3 zn{x({Il+Y3?4${Yh{)O?yr#$SJ);7HKCBOetP0&E=gWAt;aWB>sdZyk{;%*pW8(Ch1xgge1nb~h>c_^Gma_B*&*z$i z%eZ19h;ovr8&L}^#R$s!=Xc=KuZ=LXXS{O=7ag(vUEkGt z?ph9k(v_FVJDV-Y!N2bLDyv%e4PzaIJI-K{O7k;cy#gel&*;E8nb*j-o%2`uX~=w< zANdz5^z>I4%r9{!=M?!QXOJwDOqN^REP-=!viS^^kW=-B9G@gOCo|oo8{(z2a|TGd z?q0ggwPQ$UXZDtKvCQ=*bPOTUuYlAU$R7ELW&YiSelnq#O=t^0sRT&g*K7PZu~>0Y zEF=rqUvN+jX=&H*SmI;d5t}aZKV&)Y(PK$?PvEM;1)FnXQbR*Uo{%LrGB@%aHZG!- z?~Z8BxzR)qkZ2ivIim$ovp#&E;U|xdc75X#8IN8#xjvMYVc%9#Mea|QQ-$ntqCTC9 z60S?Nr=^D272Gd09{bvX%J^*=!-_XqvlajkieOO74yM>KIEn>q+y?rNlTlbA8cvla+^8VsQvEh&8gQti&73T`X=?}R`x!FQ^4+&KdHtvLp9oX(W9*V zoohiXfO(uI)X(?I_D`KgY&Sv(eDx>J2{YCT0JlFf6<|HVjP57Kr%oO8Xik6o6kg(2E zOp0RaOAGe<9EY9xn`fj6`LSYW^o%+umz+KIuz>YUopt9kK*hQslayEm&34Reib0>D z{p$kl!nCWw8Okn#e_8pll3cFk0_~k+B^4C~<@8;I9!IOlyb6Ds1>iSh@{y<^oiCFh zl|+bJJ~?BX)t^g#FYa5}&!GszcfuTEw_=&u<-CG`uOhp7WZy&vIJC@{7_AveR?Rt- ztS{@D`9P{q9UMU!mkbV7tW&RNi8-o=RHpLT6QU|7`E0aRbB`#xY92RyHonpHi}KmO zz)<;Yj8(l!sFuoSjS~z$hh{aoaoA-uU710fjdSmH-W#mw3G6eJbWy+etF6DLYHf~Jmye*!ESW7WK`K-N`3G@h(s_W10U zUdYpSv3N|}MQZ0cI=GTS2y-;9E}3qg71TA^A@Y_b^A07imuqVC#1&imvCbjwrNaiicpQ1y4>drve6H@fY0qo)Q%g9gKKA3^? z#8xMWHDn&^ezvHWsx+;WSeYL_;zP8|uP)Y!@q76}=<#Ajz~%}ErMsw@I51*EY0jBX z0_kp40cdSii`@n(BQr#*q>=g1?>@&_lcVg({&I$Om!yvjY_e*$%Et`fn^uj?7hLYW z$g26AFKI_M0u3}a>HT1$8scb}?XE~8M6nnOj}v7U<&4yUb~GPWYeZR%Fvy`AJIcN^~%-^pVg8kt*|+96v3A9 z&Q2td!{%A_7{D^5Y2Ue%-#SYQ=|` z@(!_%fyZXEC&^Cqmtg9*KVe2`&Y3(ydV*eKE|@IrceMawIX5Vr0jgXeB!V0AM7=tJjkQO{iv4L3T3-ZJD@iteafNhrGFBCN{s0QZMEGN*d7$_e^jfwb6!~cXH)n;!}FS0NW!QJU45*p`kB*i2OcT0KaYl-3ruV-Ta!d=4XTg^FZtDuD0^dA2I10i_2 zQA`ts?;xQPrCZ&88*LsZl=p9qyjs<`I+fq23e!wtXOx}LA4A2s49FIy@NM@C#hbc88Gz!GpOFq=ziy?Ov|A%BZrr5Irwm++Je#;UVd{U&Z;U~t^ z0Czl{QOS715jotgv4`S4%#0^Mp-df5ysMr#OJ%LrPkD4{W5SdF+CMUWo{{lG{~@3f zvy_`Py~L}4pDZex-86=YFtG{M$ZbzDeq2g?L+g0qc)4>uS^kNw%ReZ-L+g0jJ3%Ns z&TaH)?FM_fuJZ!hCw3> zw?nP>&7M4$w!m#5e1kN8ygU#?TdxQdbE>n3j4DWCzlH;->GVn&yToS%Y&eS-{3-&{ zHkyIs1qQCuC9+?jLF_PSI75_8oyVJ+n@zLmdT>n{-xp=!mco36*U5}5ibZ0$RLgFh zmmJ@kIfWj=5C3calyzrPZ#p!8x>E9O9sVhuj;+FSN1Rr^M z>}O*FbzY*X!gp{I6E(%IM}c&TSp-sKXS6O=T9gmmwcYeBqEV;7r92_Li_a;Cz(;!Z z{{^2HP$qr}pUtde{~JD!pMNlYL`eU?;B(Y}!Dq~&@ELCJb;)yti%zfTUZ2j|(rwXo z&7!B|7b~Dd7Rg}4Z=kKD|!3^iapG7NO`;&MoQx4 z@q3Cqe*C^nm3)8!!Mr;?O+b78$K5{Ewy zvT##P6j!f7GHyLLBL;)o3tpwyu{xx_U9RKnf55ln{~F)zgRIL0-#^MO=l^$n)BZ2< zWfvipzL#Q2XiMLLh3gx;3crYzQR!P*+!*SI;WIk}tH4s{8*Dp*DqfK5L+gw!68A3* z)FcdlmZWX(gJ~<>s`RabQT`|Tic~WAhHi%!UPfznalOgo1wXi=%p3XD3f~ihu@c3$ zhfCN(!S?gE*se`ti>lk26Xqaqo#($j2+yQEHuEnMOZIb6A7R_>D&KgZ@MkPOt{g5J z>`5?=DA6P%jzxYV28|45Cy~i;Fjn>3@*>|H@H4CA#s>ElTQjyi3HB9>KsTr3x)vE{ zRkJC`BFXHB9=>DnQR$)EgEqb)7~IS%(S4=Ls=ilJ&fIUEhy&~mmGPT=tZOiw(`&pX zB|8hy<|NfRRNdPk-Fz%_lXUYrSZ~4~lTdkql;PD$qMal}Ys@$EwR6K-gB=@X?7K`M zzmV|SZY^r zBr<-XPrQ*y(4+C&76D!<$91;h@c2<>+)t`WzS-%VF$mf};-xvrntLm5;r6cZnzBf9 z@9?T+gM7V7$Fqd~bke3b;;TQzS8`gYZ^=oa5rRPC_fQ19JpG51zQlNi(H4}8xkJc# zNqS><(=MvVQ-mG4$nL{JzX7`~2|uZm{O6@7CO2_nOF5EDXw@B%T_+x+k=-YS6D6mG zza>BwzfVszL1GjTU7q3=t8c)oz7CsNg|(3xxoOyez!ds=>-^U7zW*UU{lCTU7?Rr# z^V5GTKjXjU2fwUC#*6-<_ASO!W(>~<7&ueM*678p@z4J6`N@Z`!Qi}<_uBPE_ZIdr z`f-w8d50`dhOMDz+5Zq9Ib^&y-$*%EB35K&dP#X~Je~)i#xB7HYOlZW6aUO_n&F@D zxz^lzZflxx-psG}Nq9=UsyVvHH?^PG^Qy(JPpgiJ?#i2bB%Ycxry~y793i^pW$z5FOh!wSMcqCF(i?zXaxY$(DnrY1#3r zeeB?UH2HZggL9E-RBqiPk^wx!_g2QHVv*Sq`gJfi1s4up>uGX!d?cJ{t|q#Pgn;D$ zjxem4{4N1BEcR=1g;%(@0*B58A(MTB^F~VcQ%!a$^;I`}E+Dd@h;0tP>Bj0!6u~{Qg2bmXdXSu25b{v=CyE<|28jhk!Ea*LKcZf?AN|&!mn1@zqWsm|XGe)_;du7h=000m zh3OCTKC8YtE8H(Dw%Cx|KBDGFcV&mKs*K%|=W@WTQmr*w2m`|Ohe6o2s zH83=#&%W1$y)*{3n~7ErX@a>?5;+OA7G_U5cL2;s-7IlhAuC zHfEVT{^K!?CFb-jXMob5FSemu7p3;3l<5F}`iuSc2eM$=FUHVM#5Gwa+8u+AN)I0q zi2YvuF*2QRr8B5<^QxBFCv=8?<~8}b;+Vs$oo)*r`;dgXOyl`&wa%GmKNJ6AWiEIV zr>0H4DA=<{uX5ji#&UfF$dj(MJY2>5udr}eW62I^5~GiZJ@QmfIU%3_l7RhVazM}g zf%OrzVxvv2-zbo?*Auxkn43aUHZ!X82aw9_8(4Fh3~psETb6+jUq;=g?7EG)9d`k1 z*1repHf43(WuLb%SiCjN`J08cbPn~#h7O2LB1?USz1(jvuS*=}udD4`S8J8y&TVfk zTpk;`uiel*fJgY2+dtNAX;=47hw{R@!XF6^D#0Nu{FdyU$qiWkw;np}4}&=UcFRln z#uSvtMrD-8sO08K&Q-nEl^1^RTRFI0Mtgf(Fj|}8+hy;pTajM(Vdrw+#tQrM!aerRie8`ieRXvW zhZTNSZj)hEuWjXpU;Fm>R^rDYn*iacU2k>;=?33Qy90slzOD9<3_fzpeQWF?S$rK+ zPTY`AeC3rxlOfrB<;zERKKl9`dq^%H{U!M^a-L86EV)-1?#fwm>1%I|;lFaRvz3{= zEk8I|!;<_^6^Or;zmg6-1WT+TVPWkY_yHGvG?VBuwj%1Xf^{1U0(Bb)RCcTtc(ePv z2Y6ridoSO4?)N^vbKUR#d}q7g2l&q7J7Ax;3%MPB2u>3BJNBQ3P7K7b)kf&H0TMo~2#H>T$qjxF`>jH&8DrnF& z;i^|Wt_r@@`<1?*xtw+X-ylP*pfWZpt1?!F?^A#?pMktUY(ihz!vTAdZq{B5?wZeUkdpg|U2+Y~Yk)z<$5d=B2oghS}?aY%?+4 z&=`9~kbU&J6|>j?i=s5t+MdzfNu|F1f#pExQ9+8 zB3n2x5KV&(i3!*pB%B-YZDrGg4G_IaU$mrCCqBY zd&8e{h~{RM=P3;{>vwfMmAPa;ljU}qPn;#^$iz{5ejJb8;@a@p;+cDX(Dyl~qb{c6 zlzSpMry5<#JLCTeolLEA-d}9v&f-$hXMCYAWd;$?k!E*-H0?C=iU(?$5>y`*1cr0C znEXf4khb=p#7^Q;_K$+|{{Eu*7qnrq&*&pIXjTSyZj{}}k2nyYaX+GlEzITCtQGPh z8;Ei?M~@(xC0_j4e=^2oBRfI_B(3-Jx_Q@TWPrSb-ih;}ox1w#zRD`^xLn>_?+Bo> ztB;W1uJhL=x(*@1;pffn)WL*1`J-PAbYYbpeE#{RQYs~7oA~yv;?uqO52j>AHx87_ z+KB)U#0RxQw9u1v?arHB$y@QotF2jALd{BF!?F$t!b9xuc|MiqjI#0gbha0(^1*os zSX&482EX0`$@7p4#976^h*F#V3kUMuSm{mCc(s(8i>|D>pR;mIA3Xb*3~9y@ks0ku zLs^4oXLQoT!tp^eo5BkX89`=M*=36ARa|zxwa_Y$U%H5C)*so+xuOYlM%@2I37Jr(5 zXLssEBdl3t(DR1bb&2cwRr%qMI{rjYI@0CiXu{6}NDGNJ<1U&QLim+rnDj#u-)rs& zwWdWI{jL0a`~U=}>KnC8)S`%#{fw`q}6`$=K1GRY@$S~D8q$Y zgVQvKJoUe+;Y=-^un9hw#82dbs~!gIPZ3SiQWU}R{0R(j1~c9nI01-vhE*d2ocHW> z5g_yzX{%CJG|IA|{q2HYK+!1CcR8<8mM-=rj7pXH7dZ>Lu0n*?rESu*bDlLZ;afzc z#5>E$3VfAZS$E=@nvKOfej_{AhzHCB<`dU@XD|etraOZyAWiliSPNUSJCu$0kDMdi z<>NkDXwmql+kYxK8((%lVUgh3tG)4!jcw)+578KP(@CLAb^dIiLK+rABHn~L)&G=_ zdDnf{@%oxqu6r|RBj0}#bs(=Y-v4LP6FF)7%_V35eBoaO+&3_9#&3?e=IS~91w^e7--!k_wl`1M&8Q!ROoUeR|OjR8H}Ha8M(R~Xf%N$wh)vt7z9 zmvt6%!tGpU$T~m{Rw21+Gwcs$PdHGwC3kk9xoq~tef9@+TeA6(vlrpD&@y(PQnjUD zZ$O0=>Zc5Z@pC-P20Gi$ls@KSoF?Z;DppfGu2d7rktVK;W%9>drIt4bgXx|>lED&zS*D&s7lan_0^V6;71!&8yX)mVaOz15WVYj)uMWKe z{p#)2_(@=xvg^@cmt}NGE8aS3yWm$wBIEVrRN`7vxT?k9Ei_VWp!0;h zfW30^I096MytM10yKqgfC&}{}R!vV(lsm{o?$0}mKV(4%mQ2==dev?#CIsRYqm-i$ z$wKyLIY&6<+E&5Z8l?ZABJYAQ_ojr74EU=IHV)oGee9XT7fczbbI=C^sI>5CV zG!6t*R75xJ^133>Z-0E)#JWVg?Sa3i^oGtamGL22*kC<3kaBC6A4CGS5{c7@bVcG5g0lN&)t~w1 zu*lz;bDbGy2tmb@J~Sg}eyrA_erYtsldSND)D#MT#7Y6qyagWqHXnk35F;S2~~bozi5Zud_Z- z)|VM^<`1v7ZRzs>q@~P=wK#IQ^P)Vg$u5D;2?71y!P0IR+xpQ1W!7DC146+5+L80n zID?k3f5ApSQZmmKR(t{H?p9ZB-JV}SxKg1bm>w{TG30guW0}d{Yx1jb{t=(+u~xqk za#ieO`4KR)`aP?6yBc{HDfqRBiOA`ftM*vDJiK{We0rH&A5@5`(!06gL9nAtq@wuV z$ET6td%~EA_=_axuL#pFeXTANSx!x3BfqS`xv)U`lUbSxW~WoPOS)pG^Uf*F&5Oo@ ziSr>}roXM~B2xp{st3iutS2@52q6B9R?M{pzIPVpTF}aq7{Jb(!R`%N( z^c_H+Og{DbjPOljcb&=*;o^j}=NU~m@Qos|YP{bb(f&Z07>^Ir{4dw04@Z1YaE2k!o zX1qJ6LGKi1O^#2GUqxS$dJooD9H8#t)K}LY!@R6_-r6oY%OUlZbN;rXT9g@A zUpaZ#eFsE^arKoSe|_wgo;EWeFz=%~zk69U8i_3n%sc+Zb5Hh^n?PXR4R8DRih?7t zRe^c8_PV2+r|XPjgv_w+nhA%z8IewiPmQ>`4kylxo>PvwRP~%kB}K6K^U2?~Qgz18 z*PLxtoz16-Oh{36Zb8)<>!~^$D9TePZgHOKPue)v3ZObS1b7#BB?0;8&5wtiFW^Jb zf1bpt$<==n!f0dBe*&>CVkTn@q7|*4J@Ei`sR8IsXg*kQRud}VbGP~I%O?wLf#&hK z*L>#kxzBvM3e$(-=MBqCwQO6-JhZ`CH18lPW{O`Ufc=OCZf|PQr$xddW zLXXkZ>S!;d^%JG7caGY$9wwejV?kLRGT0Xv%A|Ia=) zxz9$tuAcfPSVEEWT9lEUPBo^T++BG%EphPD9%=Va9G{L zv%o;OHV`?k*bcDc~V0UN&h&O+m7qlcs2t-SgmCM zApe#KH>SvYp}~ia+@eT*`7+gTB^-Z4?rd9N^}9xv3OCA9AtY1j?fDZE4|#I8@eyYM zq-@duT>BZPf>O>`^;K>U4HvKO3uN`sPn7w5=O20ih)da8P=s5S3`p6c5iMY09*yJR zPx5R`<5DJXqmP^Mx4=0QELx90S0|gTd8tDm2bYCjb}QVbVIq(Fn**gAj)$xdrYI~g zKq+q_|2W@rzh^FLTc!POFg~M~)8&K~{a`xrTqVqUJ`1M=A=dr%a(1h48jEr+o-BN) zSAC`J470x-X8$yMM7|ZdJ>TL!wF6upZPmOEj_y2(%}=iFlXa@94NZOwTu4Ztb0??V z{dPT5ZcPVj<2JelN*A!oW?JU>)X;dRvqXny{U8cIUYx4%_aik63tTb6X=Squchsj_ zHKzchmk|;C64@V-(%z?o> zl;1ZPFXDb z?peWp??%7g72Sgx!1=*uzFE9{!0a$Amcbon8E0`ZMpL4Ks1|+`Wq2AVg8d5s>+FJo z=Hv`>Zs}6dZ9%-;UYR2E%{s!~B->zF&N*Z*ryW_1^};`~aGG5RUi(06pRRz7o@l)h z$~iC|;DN*v#J;nHW+wT~=>O3l4(58Uj2Mfc4~-IM9*o;`|B_)g_w_l>Dx<=mE7xRA zVCv>3Ejr!h^(N;G)z({$jh-?#Ts^%t|F3q=OvNWv-&s!kQy9KFe;xqlwb)xKyHy>Z z12-^T$&6H8&G)bxUg&vO@4%|y8HoF8xqHU?OI_A-tL9pb#;kKWAO0N;1Ff3hXzri5 z`vTv64#Z$Z3FYTH+gO5$&r$X1#OvaFoo)wl3HMojOjohVTA4rb`3{v<`SM_0Q+p_5 zRbMB?)op4YXvgJ)7%W6|rUiZM5?!93C&>H!@hecI8C>#IPyvHTSTIkRE_WEaT{yGiV(+q+Comdt5{qA<&h z1p^slV!*yHKNkQ712KEn%Z&*4)n%=)Y7Q4|MC^U(d|>Z9p8wZn%d&NBaGuZo2R4~K zIVDQmsg?0(RjUZj_hNxg$)0}|$m%S+L4}bH2sPGK+SQsterrj@n9-%6Oc9m#-AX9= z74j@|HB(8zoJBuaT^`Gqle$)yXY;?(-UVb_F|8Z zgEBSjkfD=TiM#su|B+=SZ*F|(Y|u}=XDOC0$)BSRvBgvtuLy4zq>S6^<&m*LW<9ZLKVA!79@`<`poJEX0@S?-w|=OfBKTvdAez^f~R1)_TK*usFtk{$lO7y0o*poUfb5 zVy$=QtR4vYX~`=(7W>~4IySCzhBHi|Ys-Fwdfyxp$JG4@?vPOL?GX6=?1+U6T!#il zlwlOw>6Ey*o+XEh5OHcL+L`;`_7fmw%YK5m04!3)>&X5b+3kckH8;mgzJO=a>h`8D!-^Qs zIA_5#u~Qc?FJ!!VIPie=kNSRl{RgsKvarr-9PZhR$E~t=v%?z7F*nwJd?0>d zyN|z)e(&qGts^^R4L<7)ewyKE(I5iju;CUpAwYC`Idhq_mMB8p)SpLs5Q9)hFb*S}T1b3xMeN zi2gT(Qucub<{b#`uiETf4yN4a{S!{k$QFqp1y_RaaDH>VG)JEx9>>$me`cPCzwu(a zcO_}!MN>wy58NqO+!PuYjGYyXHeevw;JgQVX24}2GwNh;15(w>&#n1j5MCMQRv zUYg@9JvK$Yr0_SVfsr$2Y0vfE>>TT!pxn>kEhR+md&b|tC^>dIt9uHSrlB2k)p@h? z#!*5Vvn>9*B$;>9&DY*^%gw(XC+^|mW?AK5B+Mn+?<3^ZO2sF0)AeQStp~YfeqI_0 zc%#bs{%2(yh~`{KxuY(l<2et#F{SUt4udt`2mt5dqY*fNb;pTT^${4*T%4!Y<-8Rivjzd1quw0!HZ|B+!|V9BdN@XDT`y>X#jSp8>DM$9=DsKL`R$+NWRB<| zINy{pnOvL4pPLG1SLa706)Sdho*VI8j2k78-ll*3^ZO91c5-HM)dH;L=u$I*I$}TM zRyR=^5`-bAr);r&MbyV#LtVP@I|iL{bhfj&yO}d;UX)58a#t=- z)BWU!0qBIo&c#mWWDk6&q>JB%4zt7guAAwh-^D7fDYwVnC`gupa^AvTq(ZZ{2VdjN z{EDl=Fb{$BU^du{A+lwpmNHT|lDgb>Jl1AE4+O(Znzu~PW^FzeltcMfSzT_=|6O7O zb0Pk$ZzCL=8((#{!{V*xS2;nlB$CpZZ1TE2xru6mY^{nLkF1kfEOE}+s{T;npXe;M zm~ZMGScu;Bt<{Ak^(`}?)UDa_feL=B^x2F8J__WcjE^$;sN$naKF0DfR`74ff7Ar= zY--|jxMUnHnZ`+m8?eh{-AtDl&UT3c_NVXP|Q{C?t@6)gT? z>XX9nvSN091>pA|B1!T=50BqJ$!+2HXhWZ9bDyc*@mJqmL+i(7EWkfbFOsH5GcN^} z+%G*ucwn@3e4tHuSex+0ZNitg318bLd{dins7?5eHsQ6^t?`-KCO+CG{85|qGup(* z+Jql$6Mn2s_=Ps%f42$OwFxh86aKJG_#FmxD*fwH!>SL9D%@?Ya)KqZL8^RXR5UlT zx0SNro#;T?L+f)he^~D0P#`zEi7_Q*uwZ8(LdFZ@VgrIrE-vMCAA5d32kZ}Fiv!2W z{DdCL9^f)80FD#ES3?BQtD4-r9?X=clB&|)$!w+u1iFA;WljxS|=?-A-UsDq6RMZqBbFgwpk}(n>ZPm zarqRD<|YbOzeZ>K3Ynra$B+;6IVEv11vaUCX91Qcc0F)Rly^ilVvF-vDaCBn?wK#~ zQa9R;^Mi)){n>=&&-s!N+M3+m$<*(BsF9+A2c%?q+87^5Oh;DreNa>1DvRlds`z>- zvT-^JD-@F4$cK4wrR!4aMP8zf)N(zWRm}x0$aPsO19bjXCunhpz%u6{>M_oni0#9r zodzE9;G&akegsLWC7B z%N2EW>QhKiW{gVAsRhpKT_k~=IuVSgtXJm!y*#VEkz_85u*Liph^e;|2MbFLz@|i+*I#xDgBUkkt znaa`#(pOF{7|UrBrX*qdmMN)3j5R<~&a$FuYkt-+|Lm&v1f|>)8uE-{wFAyXC7;GCO;+zv$ew|^4yhloIgm2!dU1%SBeEup!3!Ju4et=;&V zelV8Ao-VwMvmBQ3tTfjz;7=M)R$JmRuq9v*8QqzLg}iIsa2E~lbZj^LqlTNETix(y znty|{+YP@hVQg$!&Vw#wgXU;*X1U>ZnxjeMbv@1C$kX$1guY{c$2m=SQ(gY4#wjVQpm#_ zWu#lm?pq>ss`zWit5!z#be!^k*n1Z^sjK?`f7k`xSRBzX@s=Y&pmJFh6A^V6c3~G5 zSYb)<63p(*?hMS%jB{ak@rI#DibiIIX=Q0iX+>tX6%pPL)09k&(u$H_t!ds9ph%ya=_c{W9r-v7Qe<(Kl&#do%8kD=+0G)}{LKJv7Y2Xi{FQnz(cI z>@2_HvJ$ob7x$CM-5(<&dqDhV6+dClORI;xa9Vyo&YN3$-&K}l_d~9`zS#9Fd3e4Q zr|UQR_BY0+uHWjLdsEWh7`uJ)cF&e`{i+Miz^_>G6kVE6kxpOf=G zJG$Tcu1jdD=c8`)zvSck>k`5x;2g?zl9Uzg5iTj+`a*>R$FxcfpE@w+eR#v#h7 z>q{CGyfHxi!KsRFzI4C;CwhW6hdhwb()``qe&jut&VDP;bLv=K>;sIEu82Fjufd-V z;A!d}H;lPKBHbJQt?obe`k$BgSIa%t>pwH^FM+wI*Z)NP)tjE%;z$SU-hI0?KE=}y zEjlg623q%hmBMf^C5zaoy5l9mJ zIlmYAWq19j?%Necnn4Ztc`nd?uA((hcQLiHq}_$2TVfgTN<)+?;u0Le$eFtj)bj{!Yl6|dv0uiwUZmPZJ zjDB($l2PjKf_o>Rx zLqpyf>cu9ry}z4Pw3e1%FJOs`=RZMcC2^1zlgpm}uB%aoiQ)xm?|nymgS3H2Um#ME z_Nvyu{-Q*Vkn$YCX%HpGPV67M(`Bxt$8x{#lEyT$*PtJWS)-CNv(K-hxQZWm9fkSo zc@;0*)BP9)?NcD1x4z@v@ko-tf4r|>{@elif%11N5=Bc`{vy3o(D~#q`ETWqymB@H zSNrYWVmfO+F0Th4(#(u{Wy1?!={{0Ezuvh;vOK46malN<_ zws(r3oiBD?H1qzbCgLB{M7%|p@dm10TTPXkgEK4V%!=mVNgX&;T_1eBcT}Z&{eiCN z`~!U2-`mWs-qOot+=5U3_oOJ@6s>ZxRp{F1;6qO4itWyW54lL^G}r(=_>i=&Dqi?b z_e3i7UE4?Ub3s|Z_IX$NdA(2j6fKGUcjV_fgmVA<(TA4Q@tbkioUx6Q4FidHl}?UK zS3{giO?wT&@g2TfBQP2BPKWea{pdb(2)gY6^}GGLf&DJ%JG4bnj#zJTzmL|Tld9L8 zF|mJsOq?%dhrI3+uetX=-mEk-6wMnc`D&Ehs}!TcpHbc0a^cIZ&V$S z^)C(scVAUX9uM^9T}~Y?>Fsc;$H@KNZ&2C!{CTo?5_xk9HJ3S3;&+&`!%R={S3v7;$4avtsdsZplpk&VIl6;?$GrP5gPdwOV!vvfLN?Lb8|m`@ zw!Yrl{Xt4qB;B^Y>Gik2Q~&eHUfm~tK@)iEe_F+<)!u$C^U--%K2iILSFkO~q9@|7 zW$A~{WTsR95{=s7y!qwz!Ul=>k_zO#{u56l=mQDLUDVnOCHoc3`eDY^X(j2>|FrUQ zE0tTdn0JzNv|c-b+E#f}|1irx_}8AVJ>#g9D!+R}iP>Bo{IU@TOkTp2-c4H7T2Xnh zdma_w^rn6;sgfTTZhV_)PxOulwbAWu^LJF$4mqttmIE&a^R`0$Va%7@zX#O*JIss# zaXhPpf!&)U71q~ie=pCPB74JFk)xdi-@LmnPR|D_Wz5Q-kT29UsWlq!NGIARhMIg;u~?!# z911iQ1q!6HvGBCOm~=jRQ!LoL(xO*~<54es!M8VjB$$o_3jRE7UfsN!K!&XJ$wIGh zao7`l>FDZkAQo+lW&+`k=5RPf3bVbuPM8r(q{;K7$rCGlkxV8zZPKJzqB$6gB+{8_ zr&Ux;_RS2&$7TYFWH=rO1v9~>VA>U8sBh8P^?`6Il}M>fTxNoqYnbHp&%jOXky9GGRd~O5+D(6J|wY;drpk)ew>5 z)M6^&YfgoO$jGu_#ux5LMpMB|G!b|IEV6G*`r_XIV|>Zt7b1#M3;U zMKavlCs3j}6V6Pa1mR$tg=vlE0j^+#kV6HZb60k7EtHIhnSkq{Pyb4&fl*G9 zM|q+!-uckVX==#)eB*}_s$&_ILA<=@&`TsWi<7ZLFf<|G14ug5>80B2sf}got9=sB z-#}cDMs6XFLw&W4jSb6|ETb3?rf^_&IF;}<1Ur?tqB?Zt2!%7jXe^yCMm!M@`{pmMulJ2vI$=!P1XWY*v@!Fh zjalT2dL49Ggi?i5X`h#4>fyqyYo(<3gk_mTVp+_!lIze;&X&1aML04M8dAbXv!Y2t zQKW8C)>WIYwx+7u*F=wA7Vbcoro94IEpDt`u(0l2eqwRK{NfboI?GzfJSi#N{P&3n zWSei|ie#%#Uz5IxlI)3+&54rQiA~Wse#rgAOh?8yG12Poo@_GgVtYcYJIYRa(wH>L zdW@0Qk4KZIO`*=tlY9gM=Z(syVxz`8?QwyV8TqyC=hLyDk6Oxk#zLbmaFV5%p{N%( z)G}+!dUMUl z)|`zg+q&&@VMvm}rrDA!T(vj(T9C5+lRyIyU-x|NDe}8t2DM$m=WNkf$lk59#w1mT!{G2;c0Q#zIwlCQ6|i&0_He zR^Qo+YZfkDwy1Go-Tc{@2l3|c8NMtXGjVG2ljVb{Tr+xDi0q@yP&OA@gcEUtiW`U@5uN+t^PX9^Pye7)uNLjrP zrMokJhn6waER!a{*7Yrj$2tROC&@AQoQ9D9q)=Fzz*k*UU(;Aqs2)AFP$vUef3A1S zMDRwE!g!N!9w~-mnGB}V;eb2~aeN4AI-J)B`RaG=7mfD|norfbjjKEr&Ka2f>`hDQ{O-AZ8!nM)k!aZJ^`8HV#(uuyZig2&hs#vbp2p+=jq zp$IeCi{?pInn_BaZc#wSV1T$qzGq5XwPcw6j2k;9J$8IxY&_9E_W0uoR$a3&Fk@+< zbb+cxGsg#dXFa+O)Yr|cYYa?g3E+RQJ_-}v|G(FVv?*HK78L$(t_==(71lyjs@{c< z-UShF9qE5Uj@l4s*U9>Kt#Rs#%QC%vi_Xx;ufA?>O)KzfNB3AFxQb7Z0>imDA9r_7zaZTnGS|7jv@3%v5f9P$7 zLM`<+!@zpgi801sHvAutIhunkbL)nli1(>>4L5X&zP+R{y8J&V;WMii)o28r-!E@x z1sdg95vZvrz~n&9{OaEE_@9ngvzT;viz_VB*b-{m7F6*mw1DX&SJ~c}?#-Q-F_UPC zw%V|+g<}Tv=1_bYrU1)^ysY5;)y-d2v#>ExH@|U#n-Hc0$xZsPAhsyAn3 zIx}gRnJ|U=vvh1eGTv){SzS&@y@k6XTdP^wwSu_s%eK4`{muP7IuNe7+nr7c8FNIPQewi>k=g zvsIDT)3xXDuIA`|Hf6Ep>C{EBN3XIds_XyE^px}z?1c%EI5nZxKe5>hBUNCWo5eAi zE%I|k^SVOuq-I*6c$r-5Ri2FMIZW7!)`Sm0$3Xj`@|FH3=OlPfCb&B!Z+C9dyMkO`JtK0CocGLepEqN~ExYbM+p=G+;7`G;!_ zexQ2G*IfOA*{qq4Nne1)p#LWXG`W=8e2gYh-ro!@hP6jCkw7LAO?x{+!5IBF z)QOn8C1MvaKL=*<(32q69Qw@vExmUMM?q~n#KQeYddRH>_fMu?_s}Z&1 zE#?MT3Qs1e)4YtN18#Z3=Bxp?cv`6EK8>7@5%SW@Kf7<3d;_MF;pS)y8;q```#qwN z*`{!&JGe%Lk2T zYcE>%W%vrS3+DYT^p@vbEfliotz)^UPDIACj6AkaG@sJRms~q4i(EmZ6}-A;^_qG3 zDy`ziDR3gS((7Mpm;CZYUr7oehEZ?%j;u*~3yVyJ+#`Zv`fA8qSyl;5L-dhJFJsM& zkwH|{*p9J*Ot94x_Wcs*T@J~wZF}p)EvZDCTO;?X3Hiukm#BO#Vb-zfHJB^;7I(Eu zC2J68-98cU_GlEfux(bn#2*M{Q*0V|y{>4HhVa$4!cG}g0Q!#?DwGI&s~&C{y>HFw zIjUFpcsHHv7h0L^Q*xGyW!o6{Xb7PzkuJnn&NBl zhMT;o4Xm`uLUFi!%O|v0+E2%;o(l3e-)$XxYk9rWmmlXbYG~6!Rp#r87M8{=jIv(& zXD+O%YODz~R?Vod3DnIB%wNzLs993CsBw{#C${pmbdI&jHax~>&1_b48Wz^gt6I1; zFt=uDfUUR%b@NGO9=nj(A1=7J*d1uBS<>hd)Phm|UN~=#y4wX`ZGaaZpiFpiiq?r5 zIoGhLv1(pJpt@#O)#Ccbz|6%97i#abaEHOFzn$D$fa0tb9-Gfh!9)4zZzK(99@)Uy zM-RD8)_ju}dYP-}h2E}$YqQ=xg#mQxDq9XN>=XEW_Pl(| z`E?BwI6$#7iq@Wd+SDl)!D-13sTrHD`)agJ(JMTNq}F`3saL!;Ve7z@%nM)GWC(CF zg2ANOt;^e@ZgKfte)4ioi^te8=Kx7OYgboN5t`r21kx>UG1jN#!~RGvJm zpV)p+?*QsLlosZbNV0YzYy_gvZoN5QO9S-M-uwAn57ayR>wTrKWpAyYvzn_dj^2*v zs=B(rXMW5M1g$E(PM+_*UWGdGaRu$85BdgbE~tDq`rS5%*#QOp(LcN!a0;WCGc&x} z?6oV+e5OhIM0Wc7UD7RRpnT2cOXj8JJ;CfMo459MNA3Fkt2w1oP(2iSnl}U%p6l#E zE6CDBmTiSNqK0m@7V&1QnFw;&$?g2y*~RcSr2cgdipF8DMU`mpZf(DLfwNw{r-uUR ztlJ!F$;M)xZcdOdPoX}GyBy~=Kt-%5NWL|$X zr#h3FL~AOTjIdQyh~w(jOUGI-jiHpcb2PrR1!K|z$T-cJ-)8dK!;4o}-K$2t!9PRZ z@>WCM7E*ruNK}63$Wt*z##Nt(y8O-b3=MbgE*~x5m3w7EUC<5uUi+G&({uCj|Ak%b zI&uFLW);?b@=qL4motO%1aMxz*_BJrjTbben`wFB@>8BuIIUK&Wr~k3*-WTc?)uu> zE?wCpHepgZP1~6|Hbt&V@15lY#<|lt*lr=6PYW^m8k5!#cTUy9`5Xk$X*Wt7(m;@A zL&-gcts`fiC*o;WNA9?kSB#?hzyK#6$Xiiz`yX=PzK-}z3sf~FvKc*-M@b-@s1)UT zg7dILEZ9fkG&ePQEX)_ZbY`;dBg3_*&|!B@A$(n&$up-MSL?pl;k`Z?NOy90v~A+ViHb9; zsayc+LQ~>CX0S8n##)5s!_}zSEJs^8Uz_35BHx zXQZm;_#tiHU+m(ugcr)*?bBiR*Bdt(Bk;|I?m^ue*VQ_{!f0df4RHuwKgrLeaFhk6IMR#=HN;R{=}#gV-Gy-~G3t<}1|sW|Jsx@OT#>-}G<+|iiJ zeP>bc=bnDo#rO36YyIwj)~-9@HttVdn#+yjGm=l&l3cC~?t}XNKl$kE z%Qf=R_bZJ0uFo)I{<=T!?iarQ{eN}+2Jv&3d7phyA6@6a|96>BYvh{$Jk1W`eDdSe z$y)D||1Rb~PjN5*eIVXh6n3Ed|I&~2vtufzboj=Ed}Ah0oP0{FZ%m92UrzAd1Z zS25#pS5YjIMfAvcy4`_AtT>rsC76?dup=5)`!b^<)np=;Xw_9R93&3M=LwROBUq5?>fOWmh=ctVP0NL^(ZZq#g85YF2zO8SB_ zaPj3Bx@);KR++9`m~=YZM$~jB(#BIW5qHEcm`TtQnGDB31Xh=YXeJvXXvQRyf8kLZFq4(`hK zpyY>DdS6qpsS}4ywiwmG7arOa43Sz>kQ3Gjk6H$DLjmc?O~JONL;}S5Pv9&aaG!8Y zfl^K+VJfWPX=)#LB^A>|0coJ7Fi(`r!@`t;Na5z7x*1eXo5JB19OPWz*?+~2P)>oR z6M*cxOO7?_iloEHo7`{x@wA2Rh&K7H&33L zqSO!$#}_&7?4s-3j*l&Unh95#q;g!LVze+<;h^rT%QFvCTCqgJxq5j=SFV6;^7EZO zgoDA>l;DVJQvx|{O0+fM&8Xp zzN|!TfVhjC(Bo5Hmxb-RYJuj z5XDYNcj6Inu$qjceY4J&R_6(*ewX2h#uBCwv;uBhQu>B331UPTs;E?`7kU8CV`!nS zB4K0&mkFNGBFB}D6U+*PDA97z-HNhJ#CLuaoJ>U$8p}Y!#YKM53(b)%uh+>t8&|?i z=zPu5R#j#ct%~#wZtU;YKXjE=S*_UT{;S z8$(%;B+DbZaN`kZA^RMiX_D(Q6N@UPgbSnw;JrH@97DL}W+@EnMu}z>CoAEh6T7O4 zWnE(>q}ZAhQV7inbS5TNoFs3S4w9>m4O+-)ig(m- zR6?{76~&QPJv|j{=fSDqYLY}q6^Qo$6d@c|8e!U0T1s6h#bH3XX)2#nx~iX4Lf^fd zp@C>7j`K5@7uBaz*{HIY%C)AeCtfN2VnC3Ito5NRZy<8TQF7G{qJT0Cp;|*2Nc0?##8H*wN|}WaQ#xu$r4KQ#M^ye$ z5TOdGbB4l1>aIt8VT$=_4pz1;6s>)>h%65SY>5MEOD+TC;fk=nI)xeE>$vpB-N>hJ)k#GW* zJ%yEWlF~JhLr03D_u2$WSBS0|qXD7x1CaFa76@yhRHn;QREmHMM1wRJ#9K!IO;bclpqhn?Byv)8kNuabvZ5x zRWrT7hu+{0-2q2}wDo?6IKi-36!jKAw67QTCwy+S7(qJiRZy#0BQ!| zRXUnX(kp^!ccAyz04hhmVU1e6+297qFsJ+xI=~AsL~lz{H180UfCM7j&T4}R=`l=5 z3Nw#sr9$Eq#6fMXF&K41`|IKKE$;$3 z4#%C~b1#W_AwBF>A->wNO8T%)l#`V6XKpF|5>92ALGnsMkoN{LgOEd9%wApbVmII+ zGG=(qA=;egZ4^yqbz7q)9SidZH-;>PJ6y>-P*7@5-Z)3h0mV`&I~ZjhVw#n3DbqFF z5pK>p?{KPFDkIzx!DuH-9qy$-NSBlA{LmEP4%&c5?O@>Iq6bfOFql$Ibmu^C81cS178bQsaVMGi46waCEWfFp?n(%CIo0eqU} z!TuTo+M)?@3mRCJ+JbGvhf2fMv=c-%J#=e}xb$e%MGG$!sv%p*O;&2Cg;(@7UNWqQ zO7L=9?EMtK=7X}9(EBa|iPGbx zW+1u;*G)fLn7+6Koc+|o%!T!gRtXSP&fRW_nwJDA0uvL}g@#YvR0c31bz3teT*gpb znTRb6_;_jTZ^?3*jWd?DU|LE&KwY@TY}HIR<7;J_+tDdF^SM=zZ^fv@3{{^8Zdw89 zPP)X76r6@?Wp2Pnp%D`t2~jIUyDpd^u`uK01V*VjK>KO6hLVD;)JZEI zqf&Uh4=txv`WBrd325b42^i|c7!KXImFcJ)NgPlTG&oP;*BbPlSDffut!#xl7wd%Y zlpoi{rs-NWA#Ifiv?f9f(L_#QwzsN9aTJ{%5Q_yNYaKjT4M%Y`Tv8jVcZTXFIm(5b za^k88>}EP2CCbE8qcWqd9>tieXvh~1a|K-aCa`Q32D?%&xx-lF20f0Dya!4`2qlST z=_Yd7(E+5@+2)Hd@5|uET5#GJ1_UQws5O4JLDMt&@#>%4RT9()%ON~n4i6)j> zBJ?r_c^2Wg!*n`?5{K$T5iNW~nE%U%%8zhD2!}ex(QhRHS_2XBW}Xr3Bp!tAjU&?~ z!mK1Bxr<~m?BvRN0E)}XmVT}QY5@+AJ*L?%fF^KU$S18ORbQkNT^sU68N7Yec2EvO z7lve1F)*++NuZnz)-lnk9`{=n{00SWLQmlQxHCg_jvNt35;fugi4<@vbTF%toLNak zjvH>=DMaVzS+(fEa0hoW{9M8e)0Bv7#vXDWjZ3i$bW2AzD7i!qlnCvEP}mL|Tp^+<2&n|3ExU}K%68F0~Gyjr6$KKX?8wJ zjx$QFSg4R*w~{*K!)%{eDjE(0HE56eR>m1pR>q^Krj^trsqoUFFBW87PA<&oQSiMz*D5JV_HabZ{y%n-7%J|sZmT;se3k;UHS=nCo0)#CifFET(vT! zXbGM+)>#r9RS2tlQizCwMm_9Ux?2q69j|PwGe}uCkUbI0#+5-gvyEjtB;2u1?4`7? zjTH%vF>Q<#OiFnSE=OtsIm3zJte}nB)Glr4O`d`NP`hY;C|QLpX5l(J6v|L-3(9nC zL#cY82XmpdTHrk~r5QU{DQ0sD%485nd2(e}=i`==Lha(DFkXP9z)rzK#?Ure7>NQT z&Ll&v3Q3#E2Ac_#DI8Kd`t<;!g2nQj&R8ol6ZR$1_nX9Ml(8eNh&B6oFyTMPwe#12j=oy{cr<4m2>X zlgc)>bQBH?pNA%x#1fkB#z(3M(Tr-k12td(ugWla$Zd(@9xH#|qLh<~CJ{e&aa;iitHNRZp)cGQm09*aF9taQ z=wu4s;%n3K2o<%HoW+AOOZ09NP7)OY+-h~4Chz3JwMLv?>SK*+B@R^*XBg3o3;N;~ z#o}Qsdh#D<@`uTc0p)J;XgdoIvrG_TP$OSF+@8kaCu5KWl1WG;;|a}Cn0+FPVnXdn zOG& zjkeiGc``mN0pds&IY_6@rt@?@ccvH@!PFgog^2B(ISr(Bh z!(3OEWMu2E_e)uP(d65QQR=3&Y^b`S7b>IK3@bo_8y*vy>|lowT)(d1OLTOiZG8zR z9lpeBZEPeN255w&dRr2sn?is#9Q2G_Y8#hQ;yMXPVnL?3Vi{3k-Rc`ysqbt$2#8fu zN+s!*%W)(6>IwpR%T1SLI1VNMNsSHsAd>55>>4T)v) zNyZ1|KJ1oEbrMR}0aS|s4H`)qaB}H{$%vc8CnKHgfKfR~?0S5o&8~phDiV}rl+``l zR(--sLFOZl3a24N>+)LGOGYsVSt-b`lmQ@M2S8hKyRQFS+bln%BZ zWwdcCDZ5k^$fB%@fNFI8iZAM$%=q0-IF#fAwXo>6>tRNbQ}l7=5gF7?*& zN15cNnw>cc73BSphJGxNS_5qDqdurBsw~UwAc-9&Etqs8b23ZA@;%9Fdors&s9Iq4 zO%KSjYwuk;mw_pnUA@IR^zB3%+1v0 zse4xZI(S49M|L=~HlBusqqwdYYSU0LSEH$9lPj69tUFvLVgzQT znj+gda8c3|;Tc}y!t&a0Wt0e%b1I!wfTgC=lT2VpWIy8{&0GtN0GvZvMx)m`^dQ>}Kw2%d` zuU)>=^aq#Kw6^-mr{;ISO=+PvuTo)I+u|xM=!2-sfLrrOMHnL~a_Kt4E=4 z$$6AM&vLDl3?iW+R)XwY6@}l;lA}B&*!PvI(HOConT6Q8WdH{2m5lGKipX(OF+>$x z8duWbY{(Rwvr1B{dpatILte6Rofb%CHL#|67$2!?;O2MfAZBh_B9lgU>o!M6;)3U9 z^i|JDYxu`eK~Sg)$({oC4NS1?1|pGkxbCUg z#2O*eB&bB(MkB7$pcmpg1C|Y>tVXCb2CUE)5oKJLM`mDrPspSJq@eYcv|GsJXXKP5c_nz8rq8(A;7zkS zNRi^Q)^T|N6I$w8Q`vMv*BEcAxP;qU1cXjtwxluDa9r70Ig7e5WJiA99z9=F*BJIP z4iGx(M5-9kWemerd`{-zNC>s~g`+&NWgwt9SNUwb70-qzdJYN!^#)*U3K4ODIV?zmiY6LSqN2*7=<`x1h>$t5&q| zK;-2*jn5248P9qaJ(OVwq!sO0w#aiC=OxlaR%>TjQjx1=TRKjwX&$@sYL4p#CV zb2OtRUGEon026u~x-;VptpGZr{I;yx2A7m1ktjo1)qo8QKt407Dg6emkGFo5s*ado zYEUJ({OoQdtc5WdAI?W#6&LNL4xWi<&lXn`BS06;Xt`6aQwowBsvsk!z;+H5j|}PB zEtcr#vG`8gFyrN;g;55mG^0P~@Kka)$}&BZ&BT>fX>l10NeH#di#n_lxC`_M>AE9P z5=x9;;2qFHIJJ=koK~WK=+dlnVJPBCNisWEO)-1dxjmS1b5;gP%F5VH-C=vk3dtm# z0Rh}o(Eu4X9uao55Uz@;y#XbyT@(idg^PGvGRhZ9>JDAtYF4ZMAqOd=kLXdDB=S+0 z!$q1E4e7yA+yoOD!R<-w2NM-6i!kHzo5JLg6BW)?F?E(AgC(i~kKLc398K>qlC+)y zTt%~*sU~cvGve7UZG}sec30|lYNeA3BYW9a9z`2t{jnE8ic&IwdJ(l`fvX7zq&BlG z3v=iIXgedzarA6#YspNnzmfU1F<5sj-Z*6rj<~MhB@wxqqQX! zQH=q0!Nze6&>QuvsH@FYTGv#@qRno#0wR-yrW+-2sd%&$M~Iv3}NV-9a!`#;FC8)zK$X#V#fBc5}#RJKMTAC?07$Ihv!l zD}%H-o45+i(jukiw+kY2l5)^S?Ti8Pr7BT~+S37QBA{{u*M0dxVZM8FHgqOO*Fs$D z=qGZ?u`5NV!rQ6ecDJ2?tFJN{ag~y^6^=3^6ATAc&9kL>aP(-c4!3KTghLD_Dd;O8 zUvwXi)=PlPwB{5DzgFIX`>r&+-QD&C3vTVQ^l_c0Yu8p04hbhxdWN$paP?$$`gW#Y zJcMbCIvg*7sXz?=`mQmtQ|myT8a6uVS>%jYF!j+bXN0U~$2;g-jds_ccTT_H_WbZ$ z&;Ie-wYQx9lP5m5Vb@2h?7Y5rTEEyG^{sKg+%)Ox89xiZ{Gm@(pL*e>)91bT_}kmR zxAmD%T>jN(zW4mp@72D3-hCfE?TB4N?s@f)SC`(j;D~SE_VKf>A9;D(ub=wZ(Ra)! zDZO>mHxAsrEs?Albl%!SH{7*%{GWZ2@wCoXy|bHTRMcRu@p(|f-2 z+cl>g`p}r$4|w*b8$KL;JoD;J_dYS_)i2%~y=m`rV-`$ZH+$R5-+OAqH9Nj=@4?}l z?`(hc%fm)zOHN#x82|i)wR^u7|Hc)WIZwV__2Xy1zUBPyEjncFuh;)=)g8ZUd1>L< zXKosP(%lo+a9`X=}+$a7KKje|kVN?jPEE{P82+ne@c0D~Eo6?1vxz@cC1ZJ8I%zPI%zlciP6!``vdx^M&^x zG8N(sy}|~FS92;eBb4#pYrh$SB_6zA1Zyd{>$Az`O2V* z8C&PJ9J%>ZpWC-9dClDWDz-HqaO>Hzhej@Z`ROCNumAb*uZI_pKH$cuPY7Ii&XuS5 z-{|`Jk4{S7vSRqoT~Bm<;NIJgSae6#*=4a)KX~H{*Zr=(y>{&8+M9m=y{%iOG@twM zgWnx?^l6V=UG?0YZ|t6*ey+KF!B1~K@7Auno_;+sG4jq`FJJJ|$3K}_KI!n8iRQ0% zr0#g-%Og5=Uh(2-p(D#m9@;SOgUuiMdv4pt=YRb1-@XuQxb3jc9e2HT>)*DWxG449 zYaJKvxHRR{n55BPHXHQ@9>pyq=JpR&U3oiNV4-0zDwf&iKpC0{CMd(MU< zs$-Hp$(>|Q@+Mi6oJqzcUy?1!m1IitBw3OiNrogpk{!v7WJdBLS&^JbMkF7S4atRM zLh>M4kQ_(`)c$IFwY%C}?X9*}JFAV=zG_>wtJ+lUskT%*stwhCYCE-?+Dz@Gwo*H( zjnqDB8?}qtMD3xrP&=p%RR2{oX7Y0lvuo?-%&ng{e?i093l}vm{^&VNmYy4IYUZe5 zYb3g2WvnfpNM6X%lI*JXj?UE;lTSK%$|)Z?b?Rv+PAcxp`aY=N-y-iNYG3|+=4f1t z!|(R{vc9@W3j%JnS_VRZky<|$Ki~VImjQ0@s|giT0&J(ce}+OMI2u^LrGPJ|BK7Wc zaXQ`&oi^v4Z2g`Jvp!U>B21YwEs)P+Q9AOj9xx%@nK4jG6{k!K=$C5(eP8SKvc*03 z^EXW0GzTt}P&wmXL*Z5J06Rn!1~afmV|PHWm*>l=>58{Xy#Kwr>H#7Yy#c;|jNW(3 z_rE8Ln5hMmiT&>}5f2UE?S89b|InUw?)Z&d28!!tUX-i_dmGTln0TmJ!{ zuk3R@IUm>iU=o&X>d9RJcf(z9^NrjuBb}Ria?@ei7kY9XuM^A1uT=LDH++NrPedXN$ zHtE34uo6bLkWV=7yF5p{yC+vM41TXC*9e!u2wV>@hFx$Y?1s0&k@xiEy5Lm!sQCS! z+z9^V?5?h!TrKSQA?1a;?jyRZb5BKC6 zVA&&tgI(}WIPOvE4{nBUz@4ycIQJhT-{Rvu7p~vcliLkA{__>`=n}lKO%pcT-=m>{sLy?t`P>Px$9)C%E%9!i|8h_vDttk$>&U z-3+I~EwJ`Y$`2!0XZ44X?}Ku=N8yr@x!i8#WG8$P?mmk5cMm7t(S#F^$>nZ<`{1K6 z65yW)A4&OOMjXp~;Be&ex!m9*$lnRM-1%@EOu`PhNj#DFapB1Ex!kj`Ycl_?>5-%h zm%y?qJO|do?QjWv5=LM*?11mU^>Fx6_@6?2SoRV6E}RN8umi4#>*39C6Wjv3;7&O5 zRGtsV!B^l^IQVEdmGZ)U@CLa4v|Mg8+zq$NA3h03P9q+i3Wps-KH+HC0jI;r>GWwh zZaU?cKfD5V!JA>(8KeVi;frt+9Q{F_1E<4rXHpIrfg9mYxDzfpi|}wW9DOYLsN^}Y z1FnZ%@HV&)ZiiE=C@0(u%K|)i26_h8!X81&8IG)@-r-dE09*oh!w&c&+yu)$M0|KO?1s}} z*&NCR$H5S+g&kr&?~cPI@KG3nd*nY4IT%en!HuvR-Udg`Cp;Y2z;niMpWg^r0?T+m zJqE|YHEC-Ebou*G9SEz69^kpTKpJ^x;l;F^pVD zxnKu;6i!W3es7hjUO29Ue2ynSoya3ByNGI&f@IglnJFslyAkUvJ$N=7xqbc}7 zy93d%J|E5Po5E-6#~Dio^Tj`WmVe}&gUj2?*7(jka@q+ek4Eym_~+}(Rp?aF{`9;I zpK9h|L|)|c-)54Dt}i4~9$dPe?9bBV0DONcY# z)Bm-&HxPII4ga;cyNLVFXL@o!EaZ8%@?8J#b|Cs z9__wP?OuAvpnkPKr+i62pIPPAo)6JI;ixLFAjy5Cd&SK?IsVahm;Q#5xsrtil7%{d zWl4IdKY*lp5;42nC;1#->hmqfcKbcw^{0MNiQjbWxL?SF(CbQS{Ug_wB8?@tBT-0f zmA|Y;nHV|KeXlC_Nv=DHx0iT_sCrzI6x8^-o=U;B0@g zbUG!SiWfyENTKHDluOngAgwNJ$d^MyG*M}7K^=H3?WU2ExXP~TfuI>*0pa7l^+d6iRBuDYou>&Ah?WPALS2!L)zu8u73r)yph|Ux43&OTb%j^Ix~eHJd6XNo_Parg(1v`54e|LZu(jXh z`+)hK?ceNW^_+fER$U$-Niv60mCPSd=4O{mM}(9fbFO;|{m6|w=!nu!le>}CZrG_VM^k%Wi6eQu znRsiMbG>SEZ}pZRf2{wMzDXJy{-_vF!)XB1bDt%CSZ zJ~(}X`#T9ccxz9tRbd(1*Orvbra<@C>gz0jnZmqCmvggNigmS3!>9uB(GD z7V4m0boHai^+s zYo5%Blz8bUsUB@tZ{M~<4kpV=~09HWs7$>+1fyuJ**krptNb9b+XUamd^8U z9}@EK95UO#YsgAi#Wj)DSG;zUe%ndh>IZsq7b-5Yey#L`*@-p=R>b^M=lLt=xxv9R zK?)*TcmIw@f9Besiock2>XZ(D0Y0Pg>%#9C<-MfM3D?IM)$ec6S5J@B66S8!Ir94Y zVuGHJ-!A-~J7>Sqah`0K0fy5V<<4HO9L9LYGujXSXU}kB zv!^RxIkohwrKE_SsO@Xa%q=fHtYpAv)s>f?SK{AyYMp=0)HYb&gWd33>(@@#qTAM$qH8ZM44^w6l}T2*LETQe^kU*H|6NaRw&{X? z<6>UFR}AW_-kqN6B5r0+ubxsK9>s4Beg_xQLr7Y!cp&P<-8z7XL#BVOpqO(Bo( zE{jESvgi1ccLr=5#N-4C(Eb)4ZFA58{_f-F`d>M|WFudy{F>(by7+YS{3qXHPE{x; z`e9%j(#G_$r4txfr_S83N6acO`I?ur{RQ~ALH_Qs*iU2IAdeqga@&Aeol#zTO$kBH zC1}aj7Pj=9eC%cT)DRn+lDywW-`ag~PwuADeBJf7QLi4U_2vqdB|SWG|EYo$6o1lj zT1)L1l*=_cA7mG5UG+))%JhtWc9wgFn~#`{U0G5yV5xY9(%0JTrop+~d6vHPa|FM= z`2Ageq~B-m%@AxxW_9@-?>i=Qt(!GreDdAR$z{`QA)T9tsdV?wMok1Wf1cG&Df z+0hM&yv0|&g8{yQtZgLhbi)1vyI1+#ZyZBw4Js`sdwo_s7)br{W3VrbY0id_p4m&9 z-G0^*r59Y9y=y4+@wG!{k!H!xeo4+QZzOntb?{-sbGaAzUNo*cn+>Jut-r+jJ-N1= zNo1eARF^X^F4G`OGBky+gLH2r-MpPO8ov$rZNYB_-=!lqFxFzy2FPu}E>_&T3AdYY z&IaLnJfEHTJ#a`acO&0@^9!~EbzGA4qyk%^w%lbxdT$@$ryQEgeV%#IyURmee`=2mew*+cB3X1cbZ>h&TSYVe3;H+l3^ym= zqV$f)(&dx!ln@#-D9aPt&*bhJRt2_IiCF~l)O6BTLey`wnaX-JoBWMr&P?7FW z;YQ+@!Ozng*!}ov?<0xd6AI&7pi7lrG>Z!doNJag%L6lV{9NAaC!4PQX# zS6J=IDE;myb9l+%N~`Pd9Pj8Yry84b$! z7ScKY=v;2C(&0~j58$^OKk0G(sV;WoH{zIFj%^@U7j@Ky+T#`ckIwr$8@paA(%l=z z-c(?KdrEH{_eSUMQMXD@{+0NT@8_>PEx~^Z{?GAUe@F9);Wzw)x!gVSNce}B%wzWH zA0AN#6lVkXw{X9>9Jk>2B7UbT4$qAfcnV*;@E>t3`y}#reaQ6#^xwL{%`^;ZjtOl| z`GWjcf+@eR5N|y3`lnaMzFXseA-ziC#Rf=k3H~?yhv}((HWKen;!WebH%HC5_EDMd zVzPS}C+-%`l`oId$io!o}gO}wdkhMTvi zT%G9NcAR0y4Sere?o|wMPx;=*y+-c+JNX_>C)z~V|0Lfdc!u_9w-fJFz8A}G2LEpS z|5Mr3-mY)d{^>n{|2X{rll0Wb-XUHk@xHD6(RKsRKT0n5#&YasuTxc**Y}-}D6Lxd zjc@xON=tjxWgq&N`MRWEzG?@`*B;V}k=E`1yo?w1OKV2|w5Ae&JOj%v(#o$37Tacd zTc~t&|8d(}ACC}s@94f`@x}P1S{rtM{YwYLuD1OWP z`R&0k*3a(^{5p#K=nB3v6!aDN1^Djuv2$G?Q=Ue1Z+%gC>GG*KH{d6`%?p1v>SRv& zOkz3x&;8qq;>a(C-G*v}>GLNYptBE4@Ecy3>yYxh&2`t5mcEF{4mjDd^mKM&m} zrKMx-?B+0LZ&#Eopbcgfwd!nZgF?f5_Cp2dJ%&xpe`UW@+-{I%?)Kb7-* z{D$LKYW-VzNhpcie@&4Lu zR)b6K@dUUaOI78Pk$uDsoy2(`i@RSwdh3>T*FmM14lE;D-f?BE4N(3mx!h8&^;gN~ z`~dQg-zL7lOaH1U=|6X@!j`s)VkOaT37{WHW02}=;67SWdaiVC@zj?knoGP?rW0*1 zY3QudUcP&F6O~bXhCusz_5Ll#mE74covL!l;ppSgcbsAR^1HQ}tE)NwJ%dWOk(2%# zk8Xcabsi&~TTbIF$3IQS*1)!ul;#GmbEoH&udSrF{q$V!%>mMLd&n+dEEtrQUWXhF zm@n!6!J`-t&gAS1-?39F8CcyWjPe}d-m~2MxAMGwP^oWVfi0Y=NRr+=m1c|5r}xvj z+Cjm9@6((Zkjl%Q#BHd`<<2VMqE{cdJ@ejmFsr{cC8fU@xc*#wsQ%s|y-LnvJ!0t< z@2@eQ7xt%jm6R@2{ds!^g^tATrl%9CQNBAlyG3&R17ZH*(h0H`&c}Zb{$G&)z-6U> zc2HPUQ9;jmdo-JfTRqF>&BQIX&B%xLeTd7Ft}3dKr7E`<|0HoY6F0AmRCnF@-G<-y z26=h;C;h!zD@+^yN!>}0O&v|yYZ;6Fb$aNJvXa*a)MHg{jp+)~>L#s`|4v$!C0T!M zt-oQpzqZO>slADT)T1vz9-k%MEp?nFijLpx#wgR_)qt18MBy%w3n#;7@(? z7W^)rpUbfn?8X~*Ze8Eh{9-HjHgYfD&I+QoeiHxn_$Nxa_?P`;aA`nmSo_za>$6vo z#OMzrPiJ#RPU)e8i)E)*&)7KJQ2O$~qEqKKlJ-A-QB%$W-GIk3O78P?_+yWI<+_u6 zEN^68`QItmo|4jg2G&JxZ4T4V_YUbL&&lPE_`Wo(kU-uC? z&IGZd;Pv^b+#Ah##mTzY=pWu_c0dFE_4w1>++P4UgkJ=|>*eF-ud|Uu4p(UZlH4fF zdcy1_%-4#-tQpiBNMY_IOmb;1_lH85IsWZ~N^bNLnq96iy9hJ=+;@ML%Jl|ex(K7T zrCiIUMzsH}d&9>u&pD6th2A}~z+MM8b#FZPc5&}!?_OaoPxorM*U0%yyO%6p=L>Of zH}_6fJpKZFI`A8OLH~R*hftl^+TqQFSwooq&znyCF2(8$MlMlEBg&2 zW|tHFy}$Qr;Jq4nuLl0xHL&F+FNvh_jbmj@`h4#TczpkdJsx7$hZ~PEjy9fXoMJrP zSY@m=o^4!eTyBgQ+l;G>mm0&PynI|~*Bgx28$WOSvhjA~9mcziKQ!KJe8~7S<5R|G zjeCtR7+*8~%~)pjle6p7#(Vi3a-7FE$9nve-7h!)_ZyEe9%~$9oM1fJ*zzGSoqOv& z{;b;LX?DNTILlaXY&0%4E;F_m368}0f_#;+N_Y5cCS%lH%HL&nF9zcBvVxX1V> z<4eXjjqex-jrQv6K;yy25yqp8ql_mQCmT;Qo?)C}oNZiSY&4#03>sUFZN{|mBID)8 ztBltgKW)6x_+{f}<2Q|W8PBo)-(}ZlyyeyZkL~(C<3q+rjZYYVY5a|Guki)rYsR;X zJ;otpy!w5g@gU=2#-ohK8OItY7*94%GgcaB8|N7tjprJd8>7agvE6uy@k--0#!neH z8NX=!sqWGq_M-e#(0JC8skReXN_Ml-fG-z z{HF0P;}4A6jSm?gG5*5%E8`!G&l~Txe*TJG?=$|xIK;;LVaE3xk2H=l9&em%Jk@xH zan>ZSK4;l=y|K}Fccpj#JiA_QY%#`+X=8`+GUJuTYmJ{a-e~-?akKH;#_t<{Vtml} zi1FvfUmJgKeBSty@eSkOjXC3iHtr8I9%4M)c#Lth@dV>!&ADCLr(P48)`h%c!cqT#xcedjVBwY z8LN!7#s=eJr4&z6In@sGy87+*KOZS+m>(z)CE=>c{<%y_7A zr12Qzhm7NlzdqGVXM$ayWIWY4-8j=&Yn*RfWL#2BL3>e25CmK&Nj|FHbNWY=#P-!_)pyxKR}YnL+PFykS{!;QxnM;VVdPBflkoMx;v&N9{; z7a5ltml>Ol5o6q#G9Fp)<+I(cFEy?;o@@PVyHf1 z_@ePu6OB`hry0*Q&NR+7HW(Kh&oed~R~Roeb{H=) ze%!d;c%AVE-xAgvC^W3TCe}?e@Ti>s?>$%3WjprDb z8(WQS#!TMd?7L38zSMZ7@oM8IjlLQ$zc<+Rjm9q(}ghpYb2YQnRBEG!8Q!YCOVttZ}SyqVZ(oG-IW)#yHRTf{iPU zcD>a2F=MMSX1vDom$vJRjBAabFn-E-qwyBw?Z$5z?=fyO-fw)w_=NEpx`cwU(9jU$Xl8Alnn+Bh}Vt}BeE7^fL;bq9JmYHruFjdP9XF80d3Fn?`aW^6XDG$xJh z#*2-Y8?Q26Yy5-hf$QyhlktnjuNuE${I>CKzj2czx!MB~ZE(~K{@>E-)OyPj#BYg}ktYFuUv z8Dqwb@nYlU#z|)Xth4K>R_<%;`cuZpG_RdMYuC4#J^Tf`es;Qd|2Dfm*XsKYyWV2# zGS0E*Y_sbJjE@+18J{u+E&OlndXkNgd+qwq##fB{j5*^F+xIOu9%?+oc${&J@kHZE z##4=N*!tddyPjd3ZT!o8uO8;x^O#)pl&jJu7$G43@!Z+zAGrZHz6V)9UKJk@sdMK4^T{xXZZP_&ejDjDIn{X544|hjEClANh@kTD=`+*GCyg8_TWSC)jm` z@f729W0i4^@oeJ~W6;=Yj2Y9$)y9t-uQFb1yxw@D@fPFPjALxv_@-UoW&FNztMNhO z&x}tQpEdr$_>S#&{>iRiHoj&2hq1);)Pcrxt)7S3^`XX*#(;6GaiVdG@ib$Vah7qu zak23_8z;`Q>t^E$W2NOkY1bKJr*Vz(D&r@OpEG{Z_%-95#=DF^H2%bRzwu$?F5@;^ ze|Xxik23rE8N2?y@p3_to z&ob5+>y3+z=Nk{Ub`RNg%$PEE8ZS4lGahK=?zC~{8uR~@ag*^&#;@3T{#Coa)A(U4 z_jl~N%lKpC{l-}ZMsErSPyFSc#v~iSitZ}08WaCuhnZ{~k zow32V*m$mSnX%QFFm@O(H?A{YYy6Dy3&v44e%xl)-!$HB+-khv_=xcr#$Ox%VC*)& zWPH>3j&Vq(*UrO?BaBBHM;T8ro@6}DSY@m;o^3qGxZD^qCXK6%mm04$UTeJG*k%3p zb9VhD<5!J$7{6ouq48eh!^X#r*V{bwNxOc=_?+1v7mQch zde>`q{kQ+!+L=JfRh4=CCPo&G*cpZ$q}T++w3UUBplD}LLzbQ-5SfUttE*pk7pW~( z)k!)6HZbS_VQd!J9fHCjvbBN;0@4h!7#16pQ4Wh8mZ%`w0YMOf`R@IG_tnx>o;hcH z2lA`?f8YJyeRsdNs^5d-Y5zOJCb$ROM*i$a_+WShoC>ey_m4*to&#Iqa(E)_fNNj@ z_QBKPzrz7|3H(oZExZv9!WSw3P1Kv;6P``|_z`wX3ExBbddmHN!VkmM*!`UF|G;PA z3-Gt_HTVYnD;zgh>&tT5!Ony?;J=CRZg5{X??CD+;lp4vJQ99kzSmBk&3MH2e+R24913!uR2h z^E5v`26u&f!vo-<@CY~+&VrwZ^WkE+5>ANebxtDO0Z)c&;W~I4JPV!+FM?OV@4y@2 z58xf}C-4FI2>b;v}@&cPDg2v3J+!vT0PyaHYgzY7Q9ZSYQbFMJSgfy3}A_zc_%x4~E8 zcK8nb0DgFZ)|ZKJ65JE+2M>fj>~kDOxEW4|N5k20AzT8N!!N>P z54;~PY}I`D8R5s_Q}7wM75)~!2H%A5!YlZmW!xh5yCZCZd%#b?LtrzU0Y3-l!DHca zcp_|vJum}H@GJ0i_$vDjXA>TP7s1Qn)$lraGyDVleYX?-3H&L14E_&%4!#UugKxrj zp`jdhfD_?A!9C%A@F4gp7>7r}FTnZmv+O@CCcFaPME|*p@Rwl{_P`7*!9Ms^_%(Pg zJRe>RpXB;i5dJnimwJ0W;hW*D@DBJBct89Zd>lRnpMi_G-U#8B;cM^>_*ZE7zG?@! z3)~Iv4P)>S_-Qx=&VWb57Pt^Dfh*u@n1Csmf$QL@@Jx6Pe2aE>A>qs6-_XBJc!+xc zUBWlRAHuuf{qRxv1pF2J4crF*0Jp<8;k$4g`x-mLCb&D?A07-p<>9=ziubWVItv4a8I~DJQRKg&VZkV^Wb8* z0-gvHum|SgdUzT<6P^vf0q-Rrf5^Q2{)4pq&pJT)P3*3OtKl{9MtCdyF}#W2NevNx z2tEd%fWL;%!)@?YxE;O&%@W2p_;I)!+#4PM4~Nb0NO%mK2N%PY@JsMA>QM*blVJh& z!PDW_;U;(iybN9iuZ1_jiRAnD3Ev5~9HI9+MED{2D0~wB3O)va8J^=C*l3!j`;f|;mPnwI1A2(t#BD!1-}H_VHaEj*TVJi6nF;wI{XH_ z2wo1ag4e)DXs_239)zFZett;!$M8P*FnkRD5;!j#{|xtn`@=)wWH=4Zf}e+T;jwTjTnYab{^-xzFK=dlsU2NaV;-O2{#?fR@G~{x zS8KxW)r3FtVU0ggArF$$5B^!s2fwVI?IS_$F?zY?hdkQR9Xmn)JiWI$zi-WXd3B|G zyxyRFrj8t3)*Qx#cTm~R3e8sI zjwTp`ao7UeU^~piemDR(!$CL%hv5huh2{mW4`VP6TVNY(hk4i!2jFHn2#4S>9D$?I zyvX%o48~y#Y=iAE5BuQ&+zbcd5FCaha1@%CxIT=*IBbD!upQ=MKOBIY;UFA>!*B$S zLi4g-{~!JDP9x+0uFG+pT{w5{(Xqph@952zdSg?jG*54iPoC22f>TbLI<+}I{cz;J z>(2GF6U_DxYOQSlFwRUcn?9sxVi?F{@I@VKZ~M#f@%T`g@2Kuy zra4Y-#9r6DtNP&-&;ID~_f&W9LzIt4p|`xR`c{Qm>FvpB_v~Cbw-!A%K|aW_!u@N^ zIT9Ra_Ax`h)eFu>za0CPNow!j?(Ey=>7P4((5l*+rNN5@>2D6 zN4@Urr-bb>Ud-%uM_#PFi&q*6sAf>izhit?rGTM%Alt_G>&>F4QZ` z%@@(*HToKK|GXh@f4b=Qx_Pe4ZJG0&zPs_`zqh{)KZ7;?hn)Rx(R{+&|3>tB>9@t| z>T^!7m?XmM=mY5Py)ts{1N6-WwO@_>?$SP8J``2|EBbz3FR6Yj`egJW^iJ_>kNN2F z^=f~d%I3?Wk28ClCi2J67uI0kkG*^Eio`k1>!+!oc7+*0ANi^Zuj2m}=YJm)`<*7l zKL6JqccV9Y406$To}l^GbhYZfy&vfH zYg9iC|1F}|D@XhOPMiND*f)`%CyGAK{F8|-M^JZWHTI+brNR}QPGi5B`EWn_TI>g| zSNqSBo__RUbU$Cc96vF>pP!BWbxz;I7=OVQJ#o9}_3rC|2D+V}*RvmS{`WC)%F9pt zwmE%oa~So<>y6s=YtH_k%{5;m14zF`z3aX!_QvdL9_D*}8?whPGS41oXV*3J#66t8 zS2V9yYhxyh&eYaRj-PkB)XGaW|J{2JAd;bjUFE#L=#eQf&?ftss zY_DIXx?i7M+`!Lwv2S};?SI5wJcK@YpXz)aZblmTc?0{FOVoZQem*V>ntJy;+38KD z=?7|mBKBWE&y#N}(O06k9jf*%6mzbD|Bcui_cC2$eEmNUeWWITE=O;=Nd4RAy4qu~ zf&T}*{eP;xf2Y8c4eVdWe(+Oj@7~KO{{Paze!`A6-(+6&X|-SLQe(_s==o07-TT$# z+-F2@lKwVMd%@hyzZo>#;FLO;jp{%U0o z%fhu@xnJh}P_H%-WsWRNW0HPTlm2VOevMTz3DsZ$L}Lti#|&K?-t_{=MK>iByJ^s;r-Z& zpB(NP$HG53-APsdjF$y~t+yi=tG#c(d!yHmzlWj^Hmkj_Z^xjwU#9v|T&2zFRhM*u zYyE(o*R@a&<6P=??BlPf;NA@=AMZjRtZ6S#p$}6}e7k!I z-7v4X4*!2cA2?hS!m`ACtU)`RZ>rh%U?dV9u;qp?q+x0BDl+)r)b=Q8Yv z_SF0G^UOQZM`#x>;{S2<_yuZzJNhf=0}rY0p5ZL#-t&HH<_U+$eq+7-nd$T<(}w*b z{4YoEKScc-aC3wCKo|CdHTAz__3@L8-%rK8StI*@_0qG^`SDg|4m8kj;=02xR_DWi zqsPuy-Ora^K_7Zhb>9c>ARE87p2z6N7IW!Opl_zUe0`pZ9=}ojY@yz^pbvjW^_TJU zFHSGtkN?z({pkH_{|J7*CGSzImp`XsKU6dCy0n4+o4o(W)&IT<^FRapC%pa6YWNlj z-PXYVss`=xZTz&ItA3uu&%_4(Sge6QwSoS5r^~qPUdC$-(@J@KzLsNt2fgk+T=!)3 zQTmhH&^I;ke`N!Gt(!j(xdJ!ie{;<|^KSIED>OZ~5a*ZZ{S#ED`!H8F$Y=Y0U0Xl< z|6T2m!O#2XgRCq4czTE|gzMFxnNF8U$~V-{-T3)g1N&7C^phLtr!>&dZJ=M=Kwlv9 zLpSccrV0Ec=^Sief4|e4%-~kNFW(=&+`xW&1AT&#T{cKbQXVJE}{!;Ep{+xATPBb;?25H!-kGqTic|zPV=n zT8`dAf9}V}lbz1RDsT$+L%-4V_|gMGj1UMytHcIs7&=ypG$c0IlX`=*+9aiX)|$IN6O&u7t>u^(cb`Y`@C zpl`0JhxUEMF8>+8FU664C$YP)iF#jtT)WHZ{%U1D+(3V(f&NGQ4;-uh7n0Ar$QSJP zx=lBzAj>p&9E9Fqlb&OoUQUPqv=sZ8{lZ0#mpIXl-d%D*aCEvd8LT_54_PfzRKY<>s1-4*6{0a4c9Ot%r|4*sz-qB>w$wrf{Cvp0{mDul&-p+p1S?GtOkA77B`1)+$ zZ*2XvOjf-W`{U8u$WPzztwWDJqV_CH%!S^6zv^@FbG6e6RN#jV^at@Xyh;5WL7b=2 zN2r*7o%;&9`46@K3-NldNoKBzu zXJB9Zea!%R8}-5G|M$`3v?Jf%??rE+oyNGvFTJ1rH9dEt|50>rgXHiLS$Nuf=>MeJ zKg!A7(T6Wp{V4Rq&|6Md{SEY)PA5=-GaAfKj>AtI`z?Mxys?3wuVFvPKBV7=xfDG{ zKKOFK&g;)=Iu9UCccYspRrmS1Mf6xjy8Xwe8|ZJ~XOwmNw}`N(3{kFLjaUDFRG3ep zkA6gj?bsiKKEV3Or+*cCjB-32`?UAJO8rb{0{41@^}-q0j~u7=G90_(QuNyI6EAl4 z2crtyhSOaoa_mcYl3`l$C|J&Sl=VI=~S`QoKF-=&2dw8wj(VL z#-8m-6nlaO=Q62G7?k?*VI$XNg9aXosf}TfPGwS2nmTjqv*}!-Q?8lo2)YWnOd$8| zQ=CoZXH5^f!%~ndq`Dm|`!>In+H=`b=Se=(hDlIbv!b(d*A z%2c16VKhV4>yf4_l@7BB$wneiF;c3zLeLqO5~+02%hSggPOWh~ttKp`TPPJvaviBV zQYXT0xjCsCrA&}a=dxi@YFKA3=$2egq=U{p5N1n-KGRjOH_+Le$@EE-iclbyr7F|pu`8A>l>}*d^@JOy^RZYeq_W*cb0H|M zN#(Up74wN?Xrw@U3!!o4-f?o6ECo`NGbS%_N`XcV@}+_=aCa4}clPF+lTs5(`9dx) zH^klc8jH>bY*7Ng#}yhY{Rm*8O#f9MmDE& z(mbUKCghT?nz~DRo8EjnmDI|atTa^@bEPyAg(@FXiKV56nyOURr%KFhlkP1^t&t3- z_Ld5XY_Tg>$e81-bmWw&L4_niQ?!XI*e)gLm^Rgf$pt zNEE{v(}QG>+=H3Z+BR)guxi=zd5c?DtTfr2WVRS)GIIM#Bds(ZpJG2ay%;9*(v;R% zr8`KLHke{a{aUbm_LBL*{H61PK&sBdrN;;J7X>OVnz!5p^G;kkdr9kD6D(Z3Y|iY( z!LkJlR?J@+teib(@%+FP*QTW&Oba?vSt+G#i9DTK2qi<8C)WEkhuLJVGc@H6G#?gR zrucFyC6Ya@Qll0~ceBiuGOk*SD+|4qF{KKovR%2_JCl3Qmck9CAnC5n>3qkU&aSCJ zPbQHJH20$SLV2tor*n2+3M9W zD>me^={`Gsphz<^&&c^^-O*PHi?&~sZroPX4GB9mNJqCZtSW7v7q(FbQi?$_)twE( zbuxI^+m!LI%3N<#s<7Us&l6IheJUMk&3KQAHh=1V)b|8o($9m6VIk#`X?nBiRCZ0( zP&e^S8K7VYtdxtO}ZvWElw3>SWR@K zrDjEqE1fG!QQMT|`>IkF)d_X&S}iq|PDeUklL<4@zt@jA-%dB$N(Cvw&Z3m1Yi8U_ z#jI#d6_MFJTm*p~r|o^o$WkteY0c9mK}GEl z#+-@D!}Yo{1&}(_UFxwhvJIkWMroc`bZ3ha6pG8aa%J(|xeTA3xs23kV`qI*WBpj| zx=(FEZYo&O1(heD6#Q}>vF#;JNF~~GGE#*FpFhc-Y)nqi&toF}u!@<^t&su)U>(@A9^Eaqi4Bo5sCq_wCPZboO^G^Hey=PJK}%+5Qc zs7o@v2<%EEIy-H;zlErX?c^c42|M?(w_F}mt8YT9scn^o)H>TW*m@yTC_83EeZbP* zOh;IV`hYrfa6iU(hHi}4u_KfPh1^w`mNrx>lyaR>s_pQjS1xoVXUsZkmMkW+VZmLW z%W5TYSyLW}#ZA|k%uz=jsrY*D|8O-X{8`~f&Tk;e?WVYD}?Hso@ zsBHc^SFrPos7rADy;I!9W+#(j-gc+C0t=L+RD_Uz(xySyOB)kzKBv9cn8GV+O_Yl0 zDx=H=rO?Zr#xgsoS2`>uMGAE+| zD%wkHRy(%psw0I_nSHiQqZ(tYTvTjP#qm99US`iSpAObdt(XnEbW{|_PeEn&qqU;kmsDgd)mvv>UFqIpkLi-tSh2dz z>Ah$^>g`82#C^6F?d-PFt~IOkHd5`J8U}qk_q0n{7Skyry2{oj_h-j+X#l?e*7|H) zql;kY>b4b)nUB=oXL*24GJ|rP4{o`vwbAzVvdLik^Po$Xw^2Su_1~_KR>`zvS-w;x znO%j@&VyGjnaesMB|VsQMs9G*NQdWUfSMONJMg`Z);>Mc>LS@Qty$IGtT#$QxnC$3 zT-Au>mTo2*dF1NFqU=rRG)Aw{=2O2|ha;Qvstc%@Ol@3yi7F>)_bb6M70AwObs!4d zoXZv5)k8a#x3yd1gxPiVukP+Yx_aYc)LWLK&n9K~sht$LafJy{-Qh(mu(oeM zNzSC)?nZSlpoVUtr5%_nT4|}WSyNe8`CK04Dn?VyL-n*kmN>MAN_nZB9ajn~bk9L2 zwRN(pwdkTmWlrMlYQ*;^;fByphqaN>*^V~0`M z&5`*-CY6;Aou$vnXr|}q$DOi?P{_GOekoU;zT4ik{zk4(mfw=C ze_^?6J4vspg|g1H%T4mX?#?#NFQ2W``efy-nP_vldO}e*C*@LZ?9;qBK{xjd<7HJf z_Pv2${?hQ|g03i6*!6?f+VXxkHk5BPz*m>P%#Tj6%Y4aXyF02WapU&!vI%835bUrW z&0xzzt({EEE}P8!>7k3gGDDP_6D6b%HeFsn%9OBZw{dh*=DttiuI*-z?3LtY4M^785y37<>egaJ-%SNU zw{#lXRPFAjz16xm8s(tuv&desTL-yLP*xCrVdS>HT`w8UcwAw-wP&er%R^U?Tt0d$L8*w1nmc@C8pUer<(288)BVv)5_+sM>>7`^+r#p zc=I9kq2Fv67sG$HvFt^~flFW_lM>JBOL}f2-yUS-`>9gJSAqYpkM{db`%NT2<1l-E zNZonvTjO27wJeMFdvW=RYZQMb@jc^ZQ};=JUSD<0kWgj(9^!lYzn}4uCioA~{{Epd zzW=*&&sdFpZTwYo{w;Z?guj3P+zro}#Fw8px}!Gz2~q8D5`29BTn^6`BG@$Ayz}wp zYh8Dwho^t8hgz!-|Jib_{BNI`;p6+~WOxo@?9=b54zorEgmH}M00&lWY0{`K)Z z2W#TD5#Q7P{cGjn<9Xg$6W{;+k>?wKt&DAdTUCABTNB^^-G!(BJ*tmYlN|E^@qPLG zzr*uwo+I82>$qYwzD*Kl(fBbIFkwIazVUDn8qf z_U9`$i=z1c?~?h$5MGPv`bcZ<)9vfQ4WdVUl6v+QY~9+*Kl1#*cmv zI;;`n8ZG+QU)KwJNo4EGr$73;I^w^*qqkJXs)NS<{LWs#_BwIm``@GaXx7#qm8`i% zcfOtY0|&UEvClEGzp?T6?fY;0dnKQCwbXw{`JwE8%kOk54}V>+$k`X>JX diff --git a/build/main.o b/build/main.o index 7d4b0d5519002ea1faf435406a195b57bd64b2f4..25c08f249dcd184cd18e21eb9484f5c9adcb71d7 100644 GIT binary patch literal 116416 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 DM0TmO literal 123216 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 M?Y@lvZ?@p_{~(7 /dev/null; then + missing_deps+=("$cmd") + fi + done + + if [ ${#missing_deps[@]} -ne 0 ]; then + log_error "Missing dependencies: ${missing_deps[*]}" + echo "" + echo "Please install the missing dependencies:" + echo "- nak: https://github.com/fiatjaf/nak" + echo "- jq: sudo apt install jq (or equivalent for your system)" + exit 1 + fi + + log_success "All dependencies found" +} + +# Generate new key pairs +generate_keys() { + log_info "Generating new key pairs..." + + ADMIN_KEY=$(nak key generate) + SERVER_KEY=$(nak key generate) + + local admin_pubkey=$(echo "$ADMIN_KEY" | nak key public) + local server_pubkey=$(echo "$SERVER_KEY" | nak key public) + + log_success "New keys generated:" + echo " Admin Private Key: $ADMIN_KEY" + echo " Admin Public Key: $admin_pubkey" + echo " Server Private Key: $SERVER_KEY" + echo " Server Public Key: $server_pubkey" + echo "" + log_warning "Save these keys securely!" +} + +# Validate keys +validate_keys() { + log_info "Validating keys..." + + if [ -z "$ADMIN_KEY" ]; then + log_error "Admin key is required (use --admin-key or --generate-keys)" + exit 1 + fi + + if [ -z "$SERVER_KEY" ]; then + log_error "Server key is required (use --server-key or --generate-keys)" + exit 1 + fi + + # Validate key format (64 hex characters) + if [[ ! "$ADMIN_KEY" =~ ^[a-fA-F0-9]{64}$ ]]; then + log_error "Invalid admin key format (must be 64 hex characters)" + exit 1 + fi + + if [[ ! "$SERVER_KEY" =~ ^[a-fA-F0-9]{64}$ ]]; then + log_error "Invalid server key format (must be 64 hex characters)" + exit 1 + fi + + # Test key validity with nak + local admin_pubkey=$(echo "$ADMIN_KEY" | nak key public 2>/dev/null) + local server_pubkey=$(echo "$SERVER_KEY" | nak key public 2>/dev/null) + + if [ -z "$admin_pubkey" ]; then + log_error "Invalid admin private key" + exit 1 + fi + + if [ -z "$server_pubkey" ]; then + log_error "Invalid server private key" + exit 1 + fi + + log_success "Keys validated" +} + +# Validate configuration values +validate_config() { + log_info "Validating configuration..." + + # Validate URL format + if [[ ! "$CDN_ORIGIN" =~ ^https?:// ]]; then + log_error "CDN origin must be a valid HTTP/HTTPS URL" + exit 1 + fi + + # Validate max file size + if [[ ! "$MAX_FILE_SIZE" =~ ^[0-9]+$ ]]; then + log_error "Max file size must be a number" + exit 1 + fi + + if [ "$MAX_FILE_SIZE" -lt 1024 ]; then + log_error "Max file size must be at least 1024 bytes" + exit 1 + fi + + # Validate boolean values + if [[ ! "$NIP94_ENABLED" =~ ^(true|false)$ ]]; then + log_error "NIP94 enabled must be 'true' or 'false'" + exit 1 + fi + + if [[ ! "$AUTH_RULES_ENABLED" =~ ^(true|false)$ ]]; then + log_error "Auth rules enabled must be 'true' or 'false'" + exit 1 + fi + + # Validate cache TTL + if [[ ! "$AUTH_CACHE_TTL" =~ ^[0-9]+$ ]]; then + log_error "Cache TTL must be a number" + exit 1 + fi + + if [ "$AUTH_CACHE_TTL" -lt 60 ]; then + log_error "Cache TTL must be at least 60 seconds" + exit 1 + fi + + log_success "Configuration validated" +} + +# Create configuration event +create_config_event() { + log_info "Creating signed configuration event..." + + local expiration=$(($(date +%s) + (EXPIRATION_HOURS * 3600))) + + # Create configuration event with all settings + CONFIG_EVENT=$(nak event -k 33333 -c "Ginxsom server configuration" \ + --tag server_privkey="$SERVER_KEY" \ + --tag cdn_origin="$CDN_ORIGIN" \ + --tag max_file_size="$MAX_FILE_SIZE" \ + --tag nip94_enabled="$NIP94_ENABLED" \ + --tag auth_rules_enabled="$AUTH_RULES_ENABLED" \ + --tag auth_cache_ttl="$AUTH_CACHE_TTL" \ + --tag expiration="$expiration" \ + --sec "$ADMIN_KEY") + + if [ $? -ne 0 ]; then + log_error "Failed to create configuration event" + exit 1 + fi + + log_success "Configuration event created and signed" +} + +# Save configuration +save_config() { + if [ -z "$OUTPUT_FILE" ]; then + # Default output location + if [ -n "$XDG_CONFIG_HOME" ]; then + OUTPUT_FILE="$XDG_CONFIG_HOME/ginxsom/ginxsom_config_event.json" + else + OUTPUT_FILE="$HOME/.config/ginxsom/ginxsom_config_event.json" + fi + fi + + log_info "Saving configuration to $OUTPUT_FILE" + + # Create directory if needed + local output_dir=$(dirname "$OUTPUT_FILE") + mkdir -p "$output_dir" + + # Save formatted JSON + echo "$CONFIG_EVENT" | jq . > "$OUTPUT_FILE" + + if [ $? -ne 0 ]; then + log_error "Failed to save configuration file" + exit 1 + fi + + chmod 600 "$OUTPUT_FILE" + log_success "Configuration saved to $OUTPUT_FILE" +} + +# Display summary +show_summary() { + local admin_pubkey=$(echo "$ADMIN_KEY" | nak key public) + local server_pubkey=$(echo "$SERVER_KEY" | nak key public) + + echo "" + echo "=================================================================" + echo " GINXSOM CONFIGURATION GENERATED" + echo "=================================================================" + echo "" + log_success "Configuration file: $OUTPUT_FILE" + echo "" + echo "Configuration summary:" + echo " Admin Public Key: $admin_pubkey" + echo " Server Public Key: $server_pubkey" + echo " CDN Origin: $CDN_ORIGIN" + echo " Max File Size: $(( MAX_FILE_SIZE / 1024 / 1024 ))MB" + echo " NIP-94 Enabled: $NIP94_ENABLED" + echo " Auth Rules Enabled: $AUTH_RULES_ENABLED" + echo " Cache TTL: ${AUTH_CACHE_TTL}s" + echo " Expires: $(date -d @$(($(date +%s) + (EXPIRATION_HOURS * 3600))))" + echo "" + echo "Next steps:" + echo "1. Place config file in server's config directory" + echo "2. Set admin_pubkey in server database:" + echo " sqlite3 db/ginxsom.db << EOF" + echo " INSERT OR REPLACE INTO server_config (key, value) VALUES" + echo " ('admin_pubkey', '$admin_pubkey')," + echo " ('admin_enabled', 'true');" + echo " EOF" + echo "3. Start ginxsom server" + echo "" +} + +# Main execution +main() { + parse_args "$@" + + if [ "$GENERATE_KEYS" = "true" ]; then + check_dependencies + generate_keys + fi + + validate_keys + validate_config + create_config_event + save_config + show_summary +} + +# Run main function if script is executed directly +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi \ No newline at end of file diff --git a/scripts/setup.sh b/scripts/setup.sh new file mode 100755 index 0000000..cc92860 --- /dev/null +++ b/scripts/setup.sh @@ -0,0 +1,342 @@ +#!/bin/bash + +# Ginxsom Interactive Setup Wizard +# Creates signed configuration events for first-run server setup + +set -e + +# Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +CONFIG_PATH="${1:-}" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Helper functions +log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } +log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } + +# Check dependencies +check_dependencies() { + log_info "Checking dependencies..." + local missing_deps=() + + for cmd in nak jq; do + if ! command -v $cmd &> /dev/null; then + missing_deps+=("$cmd") + fi + done + + if [ ${#missing_deps[@]} -ne 0 ]; then + log_error "Missing dependencies: ${missing_deps[*]}" + echo "" + echo "Please install the missing dependencies:" + echo "- nak: https://github.com/fiatjaf/nak" + echo "- jq: sudo apt install jq (or equivalent for your system)" + exit 1 + fi + + log_success "All dependencies found" +} + +# Validate private key format +validate_private_key() { + local key="$1" + if [[ ! "$key" =~ ^[a-fA-F0-9]{64}$ ]]; then + return 1 + fi + return 0 +} + +# Setup key pairs with user choice +setup_keys() { + log_info "Setting up cryptographic key pairs..." + echo "" + echo "=== Admin Key Setup ===" + echo "Choose an option for your admin private key:" + echo "1. Generate a new random admin key" + echo "2. Use an existing admin private key" + echo "" + + while true; do + echo -n "Choice (1/2): " + read -r ADMIN_KEY_CHOICE + case "$ADMIN_KEY_CHOICE" in + 1) + log_info "Generating new admin key pair..." + ADMIN_PRIVKEY=$(nak key generate) + ADMIN_PUBKEY=$(echo "$ADMIN_PRIVKEY" | nak key public) + log_success "New admin key pair generated" + break + ;; + 2) + echo -n "Enter your admin private key (64 hex characters): " + read -r ADMIN_PRIVKEY + if validate_private_key "$ADMIN_PRIVKEY"; then + ADMIN_PUBKEY=$(echo "$ADMIN_PRIVKEY" | nak key public) + if [ $? -eq 0 ]; then + log_success "Admin private key validated" + break + else + log_error "Invalid private key format or nak error" + fi + else + log_error "Invalid private key format (must be 64 hex characters)" + fi + ;; + *) + log_error "Please choose 1 or 2" + ;; + esac + done + + echo "" + echo "=== Server Key Setup ===" + echo "Choose an option for your Ginxsom server private key:" + echo "1. Generate a new random server key" + echo "2. Use an existing server private key" + echo "" + + while true; do + echo -n "Choice (1/2): " + read -r SERVER_KEY_CHOICE + case "$SERVER_KEY_CHOICE" in + 1) + log_info "Generating new server key pair..." + SERVER_PRIVKEY=$(nak key generate) + SERVER_PUBKEY=$(echo "$SERVER_PRIVKEY" | nak key public) + log_success "New server key pair generated" + break + ;; + 2) + echo -n "Enter your server private key (64 hex characters): " + read -r SERVER_PRIVKEY + if validate_private_key "$SERVER_PRIVKEY"; then + SERVER_PUBKEY=$(echo "$SERVER_PRIVKEY" | nak key public) + if [ $? -eq 0 ]; then + log_success "Server private key validated" + break + else + log_error "Invalid private key format or nak error" + fi + else + log_error "Invalid private key format (must be 64 hex characters)" + fi + ;; + *) + log_error "Please choose 1 or 2" + ;; + esac + done + + echo "" + log_success "Key pairs configured:" + echo " Admin Public Key: $ADMIN_PUBKEY" + echo " Server Public Key: $SERVER_PUBKEY" + + # Save keys securely + echo "ADMIN_PRIVKEY='$ADMIN_PRIVKEY'" > "$PROJECT_ROOT/.admin_keys" + echo "ADMIN_PUBKEY='$ADMIN_PUBKEY'" >> "$PROJECT_ROOT/.admin_keys" + echo "SERVER_PRIVKEY='$SERVER_PRIVKEY'" >> "$PROJECT_ROOT/.admin_keys" + echo "SERVER_PUBKEY='$SERVER_PUBKEY'" >> "$PROJECT_ROOT/.admin_keys" + chmod 600 "$PROJECT_ROOT/.admin_keys" + + log_warning "Keys saved to $PROJECT_ROOT/.admin_keys (keep this file secure!)" +} + +# Collect server configuration +collect_configuration() { + log_info "Collecting server configuration..." + + echo "" + echo "=== Server Configuration Setup ===" + + # CDN Origin + echo -n "CDN Origin URL (default: http://localhost:9001): " + read -r CDN_ORIGIN + CDN_ORIGIN="${CDN_ORIGIN:-http://localhost:9001}" + + # Max file size + echo -n "Maximum file size in MB (default: 100): " + read -r MAX_SIZE_MB + MAX_SIZE_MB="${MAX_SIZE_MB:-100}" + MAX_FILE_SIZE=$((MAX_SIZE_MB * 1024 * 1024)) + + # NIP-94 support + echo -n "Enable NIP-94 metadata (y/n, default: y): " + read -r ENABLE_NIP94 + case "$ENABLE_NIP94" in + [Nn]*) NIP94_ENABLED="false" ;; + *) NIP94_ENABLED="true" ;; + esac + + # Authentication rules + echo -n "Enable authentication rules system (y/n, default: n): " + read -r ENABLE_AUTH_RULES + case "$ENABLE_AUTH_RULES" in + [Yy]*) AUTH_RULES_ENABLED="true" ;; + *) AUTH_RULES_ENABLED="false" ;; + esac + + # Cache TTL + echo -n "Authentication cache TTL in seconds (default: 300): " + read -r CACHE_TTL + CACHE_TTL="${CACHE_TTL:-300}" + + echo "" + log_success "Configuration collected:" + echo " CDN Origin: $CDN_ORIGIN" + echo " Max File Size: ${MAX_SIZE_MB}MB" + echo " NIP-94 Enabled: $NIP94_ENABLED" + echo " Auth Rules Enabled: $AUTH_RULES_ENABLED" + echo " Cache TTL: ${CACHE_TTL}s" +} + +# Create configuration event +create_config_event() { + log_info "Creating signed configuration event..." + + local expiration=$(($(date +%s) + 31536000)) # 1 year from now + + # Create configuration event with all settings + CONFIG_EVENT=$(nak event -k 33333 -c "Ginxsom server configuration" \ + --tag server_privkey="$SERVER_PRIVKEY" \ + --tag cdn_origin="$CDN_ORIGIN" \ + --tag max_file_size="$MAX_FILE_SIZE" \ + --tag nip94_enabled="$NIP94_ENABLED" \ + --tag auth_rules_enabled="$AUTH_RULES_ENABLED" \ + --tag auth_cache_ttl="$CACHE_TTL" \ + --tag expiration="$expiration" \ + --sec "$ADMIN_PRIVKEY") + + if [ $? -ne 0 ]; then + log_error "Failed to create configuration event" + exit 1 + fi + + log_success "Configuration event created and signed" +} + +# Save configuration file +save_config_file() { + local config_file="$1" + + log_info "Saving configuration to $config_file" + + # Create directory if it doesn't exist + local config_dir=$(dirname "$config_file") + mkdir -p "$config_dir" + + # Save configuration event to file + echo "$CONFIG_EVENT" | jq . > "$config_file" + + if [ $? -ne 0 ]; then + log_error "Failed to save configuration file" + exit 1 + fi + + chmod 600 "$config_file" + log_success "Configuration saved to $config_file" +} + +# Setup database +setup_database() { + log_info "Setting up database configuration..." + + local db_path="$PROJECT_ROOT/db/ginxsom.db" + + if [ ! -f "$db_path" ]; then + log_warning "Database not found at $db_path" + log_warning "Please ensure the database is initialized before starting the server" + return + fi + + # Insert admin configuration into database + sqlite3 "$db_path" << EOF +INSERT OR REPLACE INTO server_config (key, value, description) VALUES + ('admin_pubkey', '$ADMIN_PUBKEY', 'Admin public key from setup wizard'), + ('admin_enabled', 'true', 'Enable admin interface'); +EOF + + if [ $? -eq 0 ]; then + log_success "Database configuration updated" + else + log_warning "Failed to update database (this is OK if database doesn't exist yet)" + fi +} + +# Display setup summary +show_setup_summary() { + echo "" + echo "=================================================================" + echo " GINXSOM SETUP COMPLETE" + echo "=================================================================" + echo "" + log_success "Configuration file created: $CONFIG_PATH" + log_success "Admin keys saved: $PROJECT_ROOT/.admin_keys" + echo "" + echo "Next steps:" + echo "1. Start the Ginxsom server:" + echo " cd $PROJECT_ROOT" + echo " make run" + echo "" + echo "2. Test admin API access:" + echo " source .admin_keys" + echo " ./scripts/test_admin.sh" + echo "" + echo "3. Access web admin (when implemented):" + echo " http://localhost:9001/admin" + echo "" + log_warning "Keep the .admin_keys file secure - it contains your admin private key!" + echo "" +} + +# Main setup workflow +main() { + echo "=== Ginxsom Interactive Setup Wizard ===" + echo "" + + # Validate config path + if [ -z "$CONFIG_PATH" ]; then + # Determine default config path + if [ -n "$XDG_CONFIG_HOME" ]; then + CONFIG_PATH="$XDG_CONFIG_HOME/ginxsom/ginxsom_config_event.json" + else + CONFIG_PATH="$HOME/.config/ginxsom/ginxsom_config_event.json" + fi + fi + + log_info "Configuration will be saved to: $CONFIG_PATH" + + # Check if config already exists + if [ -f "$CONFIG_PATH" ]; then + log_warning "Configuration file already exists at $CONFIG_PATH" + echo -n "Overwrite existing configuration? (y/n): " + read -r OVERWRITE + case "$OVERWRITE" in + [Yy]*) ;; + *) log_info "Setup cancelled"; exit 0 ;; + esac + fi + + # Run setup steps + check_dependencies + setup_keys + collect_configuration + create_config_event + save_config_file "$CONFIG_PATH" + setup_database + show_setup_summary +} + +# Allow sourcing for testing individual functions +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi \ No newline at end of file diff --git a/scripts/test_admin.sh b/scripts/test_admin.sh new file mode 100755 index 0000000..bdc6345 --- /dev/null +++ b/scripts/test_admin.sh @@ -0,0 +1,296 @@ +#!/bin/bash + +# Ginxsom Admin API Test Script +# Tests admin API endpoints using nak (for Nostr events) and curl + +set -e + +# Configuration +GINXSOM_URL="http://localhost:9001" +ADMIN_PRIVKEY="${ADMIN_PRIVKEY:-}" +ADMIN_PUBKEY="" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Helper functions +log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } +log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; } + +check_dependencies() { + log_info "Checking dependencies..." + for cmd in nak curl jq; do + if ! command -v $cmd &> /dev/null; then + log_error "$cmd is not installed" + echo "" + echo "Please install missing dependencies:" + echo "- nak: https://github.com/fiatjaf/nak" + echo "- curl: standard HTTP client" + echo "- jq: JSON processor (sudo apt install jq)" + exit 1 + fi + done + log_success "All dependencies found" +} + +load_admin_keys() { + if [ -f ".admin_keys" ]; then + log_info "Loading admin keys from .admin_keys file" + source .admin_keys + fi + + if [ -z "$ADMIN_PRIVKEY" ]; then + log_error "Admin private key not found" + echo "" + echo "Please set ADMIN_PRIVKEY environment variable or create .admin_keys file:" + echo " export ADMIN_PRIVKEY='your_admin_private_key_here'" + echo "" + echo "Or run the setup wizard to generate keys:" + echo " ./scripts/setup.sh" + exit 1 + fi + + ADMIN_PUBKEY=$(echo "$ADMIN_PRIVKEY" | nak key public) + log_info "Admin public key: $ADMIN_PUBKEY" +} + +create_admin_event() { + local method="$1" + local content="admin_request" + local expiration=$(($(date +%s) + 3600)) # 1 hour from now + + # Create Nostr event with nak + local event=$(nak event -k 24242 -c "$content" \ + --tag t="$method" \ + --tag expiration="$expiration" \ + --sec "$ADMIN_PRIVKEY") + + echo "$event" +} + +send_admin_request() { + local method="$1" + local endpoint="$2" + local data="$3" + + log_info "Testing $method $endpoint" + + # Create authenticated Nostr event + local event=$(create_admin_event "$method") + local auth_header="Nostr $(echo "$event" | base64 -w 0)" + + # Send request with curl + local curl_args=(-s -w "%{http_code}" -H "Authorization: $auth_header") + + if [[ "$method" == "PUT" && -n "$data" ]]; then + curl_args+=(-H "Content-Type: application/json" -d "$data") + fi + + local response=$(curl "${curl_args[@]}" -X "$method" "$GINXSOM_URL$endpoint") + local http_code="${response: -3}" + local body="${response%???}" + + if [[ "$http_code" =~ ^2 ]]; then + log_success "$method $endpoint - HTTP $http_code" + if [[ -n "$body" ]]; then + echo "$body" | jq . 2>/dev/null || echo "$body" + fi + return 0 + else + log_error "$method $endpoint - HTTP $http_code" + echo "$body" | jq . 2>/dev/null || echo "$body" + return 1 + fi +} + +test_health_endpoint() { + echo "=================================================================" + log_info "Testing Health Endpoint (no auth required)" + echo "=================================================================" + local response=$(curl -s -w "%{http_code}" "$GINXSOM_URL/api/health") + local http_code="${response: -3}" + local body="${response%???}" + + if [[ "$http_code" =~ ^2 ]]; then + log_success "GET /api/health - HTTP $http_code" + echo "$body" | jq . + return 0 + else + log_error "GET /api/health - HTTP $http_code" + echo "$body" + return 1 + fi +} + +test_stats_endpoint() { + echo "" + echo "=================================================================" + log_info "Testing Statistics Endpoint" + echo "=================================================================" + send_admin_request "GET" "/api/stats" +} + +test_config_endpoints() { + echo "" + echo "=================================================================" + log_info "Testing Configuration Endpoints" + echo "=================================================================" + + # Get current config + log_info "Getting current configuration..." + send_admin_request "GET" "/api/config" + + echo "" + + # Update config + log_info "Updating configuration..." + local config_update='{ + "max_file_size": "209715200", + "nip94_enabled": "true", + "auth_cache_ttl": "600" + }' + + if send_admin_request "PUT" "/api/config" "$config_update"; then + echo "" + log_info "Verifying configuration update..." + send_admin_request "GET" "/api/config" + fi +} + +test_files_endpoint() { + echo "" + echo "=================================================================" + log_info "Testing Files Endpoint" + echo "=================================================================" + send_admin_request "GET" "/api/files?limit=10&offset=0" +} + +verify_server_status() { + log_info "Checking if Ginxsom server is running..." + + if curl -s --connect-timeout 5 "$GINXSOM_URL/api/health" >/dev/null 2>&1; then + log_success "Server is responding" + return 0 + else + log_error "Server is not responding at $GINXSOM_URL" + echo "" + echo "Please ensure the Ginxsom server is running:" + echo " make run" + echo "" + echo "Or check if the server is running on a different port." + return 1 + fi +} + +verify_admin_config() { + log_info "Verifying admin configuration in database..." + + if [ ! -f "db/ginxsom.db" ]; then + log_warning "Database not found at db/ginxsom.db" + return 1 + fi + + local db_admin_pubkey=$(sqlite3 db/ginxsom.db "SELECT value FROM server_config WHERE key = 'admin_pubkey';" 2>/dev/null || echo "") + local admin_enabled=$(sqlite3 db/ginxsom.db "SELECT value FROM server_config WHERE key = 'admin_enabled';" 2>/dev/null || echo "") + + if [ -z "$db_admin_pubkey" ]; then + log_warning "No admin_pubkey found in database" + echo "" + echo "Configure admin access with:" + echo "sqlite3 db/ginxsom.db << EOF" + echo "INSERT OR REPLACE INTO server_config (key, value, description) VALUES" + echo " ('admin_pubkey', '$ADMIN_PUBKEY', 'Admin authorized pubkey')," + echo " ('admin_enabled', 'true', 'Enable admin interface');" + echo "EOF" + return 1 + fi + + if [ "$db_admin_pubkey" != "$ADMIN_PUBKEY" ]; then + log_warning "Admin pubkey mismatch!" + echo " Database: $db_admin_pubkey" + echo " Current: $ADMIN_PUBKEY" + return 1 + fi + + if [ "$admin_enabled" != "true" ]; then + log_warning "Admin interface is disabled in database" + return 1 + fi + + log_success "Admin configuration verified" + return 0 +} + +show_test_summary() { + echo "" + echo "=================================================================" + log_success "Admin API testing complete!" + echo "=================================================================" + echo "" + echo "Admin credentials:" + echo " Private Key: $ADMIN_PRIVKEY" + echo " Public Key: $ADMIN_PUBKEY" + echo "" + echo "Next steps:" + echo "1. Implement web admin interface" + echo "2. Set up monitoring dashboards" + echo "3. Configure additional admin features" + echo "" +} + +main() { + echo "=== Ginxsom Admin API Test Suite ===" + echo "" + + check_dependencies + load_admin_keys + + if ! verify_server_status; then + exit 1 + fi + + if ! verify_admin_config; then + log_warning "Admin configuration issues detected - some tests may fail" + echo "" + fi + + # Run API tests + local failed_tests=0 + + if ! test_health_endpoint; then + ((failed_tests++)) + fi + + if ! test_stats_endpoint; then + ((failed_tests++)) + fi + + if ! test_config_endpoints; then + ((failed_tests++)) + fi + + if ! test_files_endpoint; then + ((failed_tests++)) + fi + + show_test_summary + + if [ $failed_tests -gt 0 ]; then + log_warning "$failed_tests tests failed" + exit 1 + else + log_success "All tests passed!" + exit 0 + fi +} + +# Allow sourcing for individual function testing +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi \ No newline at end of file diff --git a/src/admin_api.c b/src/admin_api.c new file mode 100644 index 0000000..3ee9e2b --- /dev/null +++ b/src/admin_api.c @@ -0,0 +1,688 @@ +#include +#include +#include +#include +#include +#ifdef __linux__ +#include +#else +#include +#endif +#include +#include "admin_api.h" +#include "ginxsom.h" + +// Database path (consistent with main.c) +#define DB_PATH "db/ginxsom.db" + +// Forward declarations for local utility functions +static int admin_nip94_get_origin(char* out, size_t out_size); +static void admin_nip94_build_blob_url(const char* origin, const char* sha256, const char* mime_type, char* out, size_t out_size); +static const char* admin_mime_to_extension(const char* mime_type); + +// Local utility functions (from main.c but implemented here for admin API) +static int admin_nip94_get_origin(char* out, size_t out_size) { + if (!out || out_size == 0) { + return 0; + } + + sqlite3* db; + sqlite3_stmt* stmt; + int rc; + + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc) { + // Default on DB error + strncpy(out, "http://localhost:9001", out_size - 1); + out[out_size - 1] = '\0'; + return 1; + } + + const char* sql = "SELECT value FROM server_config WHERE key = 'cdn_origin'"; + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + const char* value = (const char*)sqlite3_column_text(stmt, 0); + if (value) { + strncpy(out, value, out_size - 1); + out[out_size - 1] = '\0'; + sqlite3_finalize(stmt); + sqlite3_close(db); + return 1; + } + } + sqlite3_finalize(stmt); + } + + sqlite3_close(db); + + // Default fallback + strncpy(out, "http://localhost:9001", out_size - 1); + out[out_size - 1] = '\0'; + return 1; +} + +static void admin_nip94_build_blob_url(const char* origin, const char* sha256, + const char* mime_type, char* out, size_t out_size) { + if (!origin || !sha256 || !out || out_size == 0) { + return; + } + + // Use local admin implementation for extension mapping + const char* extension = admin_mime_to_extension(mime_type); + snprintf(out, out_size, "%s/%s%s", origin, sha256, extension); +} + +// Centralized MIME type to file extension mapping (from main.c) +static const char* admin_mime_to_extension(const char* mime_type) { + if (!mime_type) { + return ".bin"; + } + + if (strstr(mime_type, "image/jpeg")) { + return ".jpg"; + } else if (strstr(mime_type, "image/webp")) { + return ".webp"; + } else if (strstr(mime_type, "image/png")) { + return ".png"; + } else if (strstr(mime_type, "image/gif")) { + return ".gif"; + } else if (strstr(mime_type, "video/mp4")) { + return ".mp4"; + } else if (strstr(mime_type, "video/webm")) { + return ".webm"; + } else if (strstr(mime_type, "audio/mpeg")) { + return ".mp3"; + } else if (strstr(mime_type, "audio/ogg")) { + return ".ogg"; + } else if (strstr(mime_type, "text/plain")) { + return ".txt"; + } else if (strstr(mime_type, "application/pdf")) { + return ".pdf"; + } else { + return ".bin"; + } +} + +// Main API request handler +void handle_admin_api_request(const char* method, const char* uri) { + const char* path = uri + 4; // Skip "/api" + + // Check if admin interface is enabled + if (!is_admin_enabled()) { + send_json_error(503, "admin_disabled", "Admin interface is disabled"); + return; + } + + // Authentication required for all admin operations except health check + if (strcmp(path, "/health") != 0) { + const char* auth_header = getenv("HTTP_AUTHORIZATION"); + if (!authenticate_admin_request(auth_header)) { + send_json_error(401, "admin_auth_required", "Valid admin authentication required"); + return; + } + } + + // Route to appropriate handler + if (strcmp(method, "GET") == 0) { + if (strcmp(path, "/stats") == 0) { + handle_stats_api(); + } else if (strcmp(path, "/config") == 0) { + handle_config_get_api(); + } else if (strncmp(path, "/files", 6) == 0) { + handle_files_api(); + } else if (strcmp(path, "/health") == 0) { + handle_health_api(); + } else { + send_json_error(404, "not_found", "API endpoint not found"); + } + } else if (strcmp(method, "PUT") == 0) { + if (strcmp(path, "/config") == 0) { + handle_config_put_api(); + } else { + send_json_error(405, "method_not_allowed", "Method not allowed"); + } + } else { + send_json_error(405, "method_not_allowed", "Method not allowed"); + } +} + +// Admin authentication functions +int authenticate_admin_request(const char* auth_header) { + if (!auth_header) { + return 0; // No auth header + } + + // Use existing authentication system with "admin" method + int auth_result = authenticate_request(auth_header, "admin", NULL); + if (auth_result != NOSTR_SUCCESS) { + return 0; // Invalid Nostr event + } + + // Extract pubkey from validated event using existing parser + char event_json[4096]; + int parse_result = parse_authorization_header(auth_header, event_json, sizeof(event_json)); + if (parse_result != NOSTR_SUCCESS) { + return 0; + } + + cJSON* event = cJSON_Parse(event_json); + if (!event) { + return 0; + } + + cJSON* pubkey_json = cJSON_GetObjectItem(event, "pubkey"); + if (!pubkey_json || !cJSON_IsString(pubkey_json)) { + cJSON_Delete(event); + return 0; + } + + const char* event_pubkey = cJSON_GetStringValue(pubkey_json); + int is_admin = verify_admin_pubkey(event_pubkey); + + cJSON_Delete(event); + return is_admin; +} + +int verify_admin_pubkey(const char* event_pubkey) { + if (!event_pubkey) { + return 0; + } + + sqlite3* db; + sqlite3_stmt* stmt; + int rc, is_admin = 0; + + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc) { + return 0; + } + + const char* sql = "SELECT value FROM server_config WHERE key = 'admin_pubkey'"; + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + const char* admin_pubkey = (const char*)sqlite3_column_text(stmt, 0); + if (admin_pubkey && strcmp(event_pubkey, admin_pubkey) == 0) { + is_admin = 1; + } + } + sqlite3_finalize(stmt); + } + sqlite3_close(db); + + return is_admin; +} + +int is_admin_enabled(void) { + sqlite3* db; + sqlite3_stmt* stmt; + int rc, enabled = 0; + + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc) { + return 0; // Default disabled if can't access DB + } + + const char* sql = "SELECT value FROM server_config WHERE key = 'admin_enabled'"; + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + const char* value = (const char*)sqlite3_column_text(stmt, 0); + enabled = (value && strcmp(value, "true") == 0) ? 1 : 0; + } + sqlite3_finalize(stmt); + } + sqlite3_close(db); + + return enabled; +} + +// Individual endpoint handlers +void handle_stats_api(void) { + sqlite3* db; + sqlite3_stmt* stmt; + int rc; + + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc) { + send_json_error(500, "database_error", "Failed to open database"); + return; + } + + // Create consolidated statistics view if it doesn't exist + const char* create_view = + "CREATE VIEW IF NOT EXISTS storage_stats AS " + "SELECT " + " COUNT(*) as total_blobs, " + " SUM(size) as total_bytes, " + " AVG(size) as avg_blob_size, " + " COUNT(DISTINCT uploader_pubkey) as unique_uploaders, " + " MIN(uploaded_at) as first_upload, " + " MAX(uploaded_at) as last_upload " + "FROM blobs"; + + rc = sqlite3_exec(db, create_view, NULL, NULL, NULL); + if (rc != SQLITE_OK) { + sqlite3_close(db); + send_json_error(500, "database_error", "Failed to create stats view"); + return; + } + + // Query storage_stats view + const char* sql = "SELECT total_blobs, total_bytes, avg_blob_size, " + "unique_uploaders, first_upload, last_upload FROM storage_stats"; + + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + sqlite3_close(db); + send_json_error(500, "database_error", "Failed to prepare query"); + return; + } + + cJSON* response = cJSON_CreateObject(); + cJSON* data = cJSON_CreateObject(); + cJSON_AddStringToObject(response, "status", "success"); + cJSON_AddItemToObject(response, "data", data); + + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + int total_files = sqlite3_column_int(stmt, 0); + long long total_bytes = sqlite3_column_int64(stmt, 1); + double avg_size = sqlite3_column_double(stmt, 2); + int unique_uploaders = sqlite3_column_int(stmt, 3); + + cJSON_AddNumberToObject(data, "total_files", total_files); + cJSON_AddNumberToObject(data, "total_bytes", (double)total_bytes); + cJSON_AddNumberToObject(data, "total_size_mb", (double)total_bytes / (1024 * 1024)); + cJSON_AddNumberToObject(data, "unique_uploaders", unique_uploaders); + cJSON_AddNumberToObject(data, "first_upload", sqlite3_column_int64(stmt, 4)); + cJSON_AddNumberToObject(data, "last_upload", sqlite3_column_int64(stmt, 5)); + cJSON_AddNumberToObject(data, "avg_file_size", avg_size); + + // Get file type distribution + sqlite3_stmt* type_stmt; + const char* type_sql = "SELECT type, COUNT(*) FROM blobs GROUP BY type ORDER BY COUNT(*) DESC LIMIT 5"; + cJSON* file_types = cJSON_CreateObject(); + + rc = sqlite3_prepare_v2(db, type_sql, -1, &type_stmt, NULL); + if (rc == SQLITE_OK) { + while (sqlite3_step(type_stmt) == SQLITE_ROW) { + const char* type_name = (const char*)sqlite3_column_text(type_stmt, 0); + int count = sqlite3_column_int(type_stmt, 1); + cJSON_AddNumberToObject(file_types, type_name ? type_name : "unknown", count); + } + sqlite3_finalize(type_stmt); + } + cJSON_AddItemToObject(data, "file_types", file_types); + } else { + // No data - return zeros + cJSON_AddNumberToObject(data, "total_files", 0); + cJSON_AddNumberToObject(data, "total_bytes", 0); + cJSON_AddNumberToObject(data, "total_size_mb", 0.0); + cJSON_AddNumberToObject(data, "unique_uploaders", 0); + cJSON_AddNumberToObject(data, "avg_file_size", 0); + cJSON_AddItemToObject(data, "file_types", cJSON_CreateObject()); + } + + sqlite3_finalize(stmt); + sqlite3_close(db); + + char* response_str = cJSON_PrintUnformatted(response); + send_json_response(200, response_str); + free(response_str); + cJSON_Delete(response); +} + +void handle_config_get_api(void) { + sqlite3* db; + sqlite3_stmt* stmt; + int rc; + + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc) { + send_json_error(500, "database_error", "Failed to open database"); + return; + } + + cJSON* response = cJSON_CreateObject(); + cJSON* data = cJSON_CreateObject(); + cJSON_AddStringToObject(response, "status", "success"); + cJSON_AddItemToObject(response, "data", data); + + // Query all server config settings + const char* sql = "SELECT key, value FROM server_config ORDER BY key"; + rc = sqlite3_prepare_v2(db, 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) { + cJSON_AddStringToObject(data, key, value); + } + } + sqlite3_finalize(stmt); + } + + sqlite3_close(db); + + char* response_str = cJSON_PrintUnformatted(response); + send_json_response(200, response_str); + free(response_str); + cJSON_Delete(response); +} + +void handle_config_put_api(void) { + // Read request body + const char* content_length_str = getenv("CONTENT_LENGTH"); + if (!content_length_str) { + send_json_error(411, "length_required", "Content-Length header required"); + return; + } + + long content_length = atol(content_length_str); + if (content_length <= 0 || content_length > 4096) { + send_json_error(400, "invalid_content_length", "Invalid content length"); + return; + } + + char* json_body = malloc(content_length + 1); + if (!json_body) { + send_json_error(500, "memory_error", "Failed to allocate memory"); + return; + } + + size_t bytes_read = fread(json_body, 1, content_length, stdin); + if (bytes_read != (size_t)content_length) { + free(json_body); + send_json_error(400, "incomplete_body", "Failed to read complete request body"); + return; + } + json_body[content_length] = '\0'; + + // Parse JSON + cJSON* config_data = cJSON_Parse(json_body); + if (!config_data) { + free(json_body); + send_json_error(400, "invalid_json", "Invalid JSON in request body"); + return; + } + + // Update database + sqlite3* db; + sqlite3_stmt* stmt; + int rc; + + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READWRITE, NULL); + if (rc) { + free(json_body); + cJSON_Delete(config_data); + send_json_error(500, "database_error", "Failed to open database"); + return; + } + + // Collect updated keys for response + cJSON* updated_keys = cJSON_CreateArray(); + + // Update each config value + const char* update_sql = "INSERT OR REPLACE INTO server_config (key, value) VALUES (?, ?)"; + + cJSON* item = NULL; + cJSON_ArrayForEach(item, config_data) { + if (cJSON_IsString(item) && item->string) { + rc = sqlite3_prepare_v2(db, update_sql, -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, item->string, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, cJSON_GetStringValue(item), -1, SQLITE_STATIC); + + rc = sqlite3_step(stmt); + if (rc == SQLITE_DONE) { + cJSON_AddItemToArray(updated_keys, cJSON_CreateString(item->string)); + } + sqlite3_finalize(stmt); + } + } + } + + free(json_body); + cJSON_Delete(config_data); + sqlite3_close(db); + + // Send response + cJSON* response = cJSON_CreateObject(); + cJSON_AddStringToObject(response, "status", "success"); + cJSON_AddStringToObject(response, "message", "Configuration updated successfully"); + cJSON_AddItemToObject(response, "updated_keys", updated_keys); + + char* response_str = cJSON_PrintUnformatted(response); + send_json_response(200, response_str); + free(response_str); + cJSON_Delete(response); +} + +void handle_files_api(void) { + // Parse query parameters for pagination + const char* query_string = getenv("QUERY_STRING"); + int limit = 50; + int offset = 0; + + if (query_string) { + char params[10][256]; + int param_count = parse_query_params(query_string, params, 10); + + for (int i = 0; i < param_count; i++) { + char* key = params[i]; + char* value = strchr(key, '='); + if (value) { + *value++ = '\0'; + if (strcmp(key, "limit") == 0) { + limit = atoi(value); + if (limit <= 0 || limit > 200) limit = 50; + } else if (strcmp(key, "offset") == 0) { + offset = atoi(value); + if (offset < 0) offset = 0; + } + } + } + } + + sqlite3* db; + sqlite3_stmt* stmt; + int rc; + + rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc) { + send_json_error(500, "database_error", "Failed to open database"); + return; + } + + // Query recent files with pagination + const char* sql = "SELECT sha256, size, type, uploaded_at, uploader_pubkey, filename " + "FROM blobs ORDER BY uploaded_at DESC LIMIT ? OFFSET ?"; + + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + sqlite3_close(db); + send_json_error(500, "database_error", "Failed to prepare query"); + return; + } + + sqlite3_bind_int(stmt, 1, limit); + sqlite3_bind_int(stmt, 2, offset); + + cJSON* response = cJSON_CreateObject(); + cJSON* data = cJSON_CreateObject(); + cJSON* files_array = cJSON_CreateArray(); + cJSON_AddStringToObject(response, "status", "success"); + cJSON_AddItemToObject(response, "data", data); + cJSON_AddItemToObject(data, "files", files_array); + cJSON_AddNumberToObject(data, "limit", limit); + cJSON_AddNumberToObject(data, "offset", offset); + + int total_count = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) { + total_count++; + + cJSON* file_obj = cJSON_CreateObject(); + cJSON_AddItemToArray(files_array, file_obj); + + const char* sha256 = (const char*)sqlite3_column_text(stmt, 0); + const char* type = (const char*)sqlite3_column_text(stmt, 2); + const char* filename = (const char*)sqlite3_column_text(stmt, 5); + + cJSON_AddStringToObject(file_obj, "sha256", sha256 ? sha256 : ""); + cJSON_AddNumberToObject(file_obj, "size", sqlite3_column_int64(stmt, 1)); + cJSON_AddStringToObject(file_obj, "type", type ? type : ""); + cJSON_AddNumberToObject(file_obj, "uploaded_at", sqlite3_column_int64(stmt, 3)); + + const char* uploader = (const char*)sqlite3_column_text(stmt, 4); + cJSON_AddStringToObject(file_obj, "uploader_pubkey", + uploader ? uploader : ""); + + cJSON_AddStringToObject(file_obj, "filename", + filename ? filename : ""); + + // Build URL for file + char url[1024]; + if (type && sha256) { + // Use local admin implementation for URL building + char origin_url[256]; + admin_nip94_get_origin(origin_url, sizeof(origin_url)); + admin_nip94_build_blob_url(origin_url, sha256, type, url, sizeof(url)); + cJSON_AddStringToObject(file_obj, "url", url); + } + } + + // Get total count for pagination info + const char* count_sql = "SELECT COUNT(*) FROM blobs"; + sqlite3_stmt* count_stmt; + + rc = sqlite3_prepare_v2(db, count_sql, -1, &count_stmt, NULL); + if (rc == SQLITE_OK) { + rc = sqlite3_step(count_stmt); + if (rc == SQLITE_ROW) { + int total = sqlite3_column_int(count_stmt, 0); + cJSON_AddNumberToObject(data, "total", total); + } + sqlite3_finalize(count_stmt); + } + + sqlite3_finalize(stmt); + sqlite3_close(db); + + char* response_str = cJSON_PrintUnformatted(response); + send_json_response(200, response_str); + free(response_str); + cJSON_Delete(response); +} + +void handle_health_api(void) { + cJSON* response = cJSON_CreateObject(); + cJSON* data = cJSON_CreateObject(); + cJSON_AddStringToObject(response, "status", "success"); + cJSON_AddItemToObject(response, "data", data); + + // Check database connection + sqlite3* db; + int rc = sqlite3_open_v2(DB_PATH, &db, SQLITE_OPEN_READONLY, NULL); + if (rc == SQLITE_OK) { + cJSON_AddStringToObject(data, "database", "connected"); + sqlite3_close(db); + } else { + cJSON_AddStringToObject(data, "database", "disconnected"); + } + + // Check blob directory + struct stat st; + if (stat("blobs", &st) == 0 && S_ISDIR(st.st_mode)) { + cJSON_AddStringToObject(data, "blob_directory", "accessible"); + } else { + cJSON_AddStringToObject(data, "blob_directory", "inaccessible"); + } + + // Get disk usage + cJSON* disk_usage = cJSON_CreateObject(); + struct statvfs vfs; + if (statvfs(".", &vfs) == 0) { + unsigned long long total_bytes = (unsigned long long)vfs.f_blocks * vfs.f_frsize; + unsigned long long free_bytes = (unsigned long long)vfs.f_bavail * vfs.f_frsize; + unsigned long long used_bytes = total_bytes - free_bytes; + double usage_percent = (double)used_bytes / (double)total_bytes * 100.0; + + cJSON_AddNumberToObject(disk_usage, "total_bytes", (double)total_bytes); + cJSON_AddNumberToObject(disk_usage, "used_bytes", (double)used_bytes); + cJSON_AddNumberToObject(disk_usage, "available_bytes", (double)free_bytes); + cJSON_AddNumberToObject(disk_usage, "usage_percent", usage_percent); + } + cJSON_AddItemToObject(data, "disk_usage", disk_usage); + + // Add server info + cJSON_AddNumberToObject(data, "server_time", (double)time(NULL)); + cJSON_AddNumberToObject(data, "uptime", 0); // Would need to track process start time + + char* response_str = cJSON_PrintUnformatted(response); + send_json_response(200, response_str); + free(response_str); + cJSON_Delete(response); +} + +// Utility functions +void send_json_response(int status, const char* json_content) { + printf("Status: %d OK\r\n", status); + printf("Content-Type: application/json\r\n"); + printf("Cache-Control: no-cache\r\n"); + printf("\r\n"); + printf("%s\n", json_content); +} + +void send_json_error(int status, const char* error, const char* message) { + cJSON* response = cJSON_CreateObject(); + cJSON_AddStringToObject(response, "status", "error"); + cJSON_AddStringToObject(response, "error", error); + cJSON_AddStringToObject(response, "message", message); + + char* response_str = cJSON_PrintUnformatted(response); + printf("Status: %d %s\r\n", + status, + status == 400 ? "Bad Request" : + status == 401 ? "Unauthorized" : + status == 403 ? "Forbidden" : + status == 404 ? "Not Found" : + status == 500 ? "Internal Server Error" : + status == 503 ? "Service Unavailable" : + "Error"); + printf("Content-Type: application/json\r\n"); + printf("Cache-Control: no-cache\r\n"); + printf("\r\n"); + printf("%s\n", response_str); + + free(response_str); + cJSON_Delete(response); +} + +int parse_query_params(const char* query_string, char params[][256], int max_params) { + if (!query_string || !params) return 0; + + size_t query_len = strlen(query_string); + char* query_copy = malloc(query_len + 1); + if (!query_copy) return 0; + memcpy(query_copy, query_string, query_len + 1); + + int count = 0; + char* token = strtok(query_copy, "&"); + + while (token && count < max_params) { + if (strlen(token) >= sizeof(params[0])) { + token[sizeof(params[0]) - 1] = '\0'; + } + strcpy(params[count], token); + count++; + token = strtok(NULL, "&"); + } + + free(query_copy); + return count; +} \ No newline at end of file diff --git a/src/admin_api.h b/src/admin_api.h new file mode 100644 index 0000000..1bc0b4e --- /dev/null +++ b/src/admin_api.h @@ -0,0 +1,26 @@ +#ifndef ADMIN_API_H +#define ADMIN_API_H + +#include "ginxsom.h" + +// Main API request handler +void handle_admin_api_request(const char* method, const char* uri); + +// Individual endpoint handlers +void handle_stats_api(void); +void handle_config_get_api(void); +void handle_config_put_api(void); +void handle_files_api(void); +void handle_health_api(void); + +// Admin authentication functions +int authenticate_admin_request(const char* auth_header); +int is_admin_enabled(void); +int verify_admin_pubkey(const char* event_pubkey); + +// Utility functions +void send_json_response(int status, const char* json_content); +void send_json_error(int status, const char* error, const char* message); +int parse_query_params(const char* query_string, char params[][256], int max_params); + +#endif \ No newline at end of file diff --git a/src/main.c b/src/main.c index 736e852..96abc37 100644 --- a/src/main.c +++ b/src/main.c @@ -1528,7 +1528,26 @@ void handle_head_upload_request(void) { int auth_result = nostr_validate_request(&request, &result); if (auth_result != NOSTR_SUCCESS || !result.valid) { - send_upload_error_response(401, "authentication_failed", "Invalid or expired authentication", XREASON_AUTH_INVALID); + 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; } @@ -1915,8 +1934,20 @@ void handle_list_request(const char* pubkey) { int auth_result = nostr_validate_request(&request, &result); if (auth_result != NOSTR_SUCCESS || !result.valid) { - send_error_response(401, "authentication_failed", "Invalid or expired authentication", - "The provided Nostr event is invalid, expired, or does not authorize this operation"); + 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"; + + // 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("GET", "/list", "failed", 401); return; } @@ -2382,8 +2413,20 @@ void handle_delete_request(const char* sha256) { int auth_result = nostr_validate_request(&request, &result); if (auth_result != NOSTR_SUCCESS || !result.valid) { - send_error_response(401, "authentication_failed", "Invalid or expired authentication", - "The provided Nostr event is invalid, expired, or does not authorize this operation"); + 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"; + + // 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("DELETE", "/delete", "failed", 401); return; } @@ -2669,57 +2712,43 @@ void handle_upload_request(void) { auth_result, result.valid, result.reason); if (auth_result == NOSTR_SUCCESS && !result.valid) { - auth_result = result.error_code; - if (auth_result != NOSTR_SUCCESS) { - free(file_data); - - // Provide specific error messages based on the authentication failure type - const char* error_type = "authentication_failed"; - const char* message = "Authentication failed"; - const char* details = "The request failed nostr authentication"; - - switch (auth_result) { - case NOSTR_ERROR_EVENT_INVALID_CONTENT: - error_type = "event_expired"; - message = "Authentication event expired"; - details = "The provided nostr event has expired and is no longer valid"; - break; - case NOSTR_ERROR_EVENT_INVALID_SIGNATURE: - error_type = "invalid_signature"; - message = "Invalid cryptographic signature"; - details = "The event signature verification failed"; - break; - case NOSTR_ERROR_EVENT_INVALID_PUBKEY: - error_type = "invalid_pubkey"; - message = "Invalid public key"; - details = "The event contains an invalid or malformed public key"; - break; - case NOSTR_ERROR_EVENT_INVALID_ID: - error_type = "invalid_event_id"; - message = "Invalid event ID"; - details = "The event ID does not match the calculated hash"; - break; - case NOSTR_ERROR_INVALID_INPUT: - error_type = "invalid_format"; - message = "Invalid authorization format"; - details = "The authorization header format is invalid or malformed"; - break; - default: - error_type = "authentication_failed"; - message = "Authentication failed"; - // Use C-style string formatting for error details - static char error_details_buffer[256]; - snprintf(error_details_buffer, sizeof(error_details_buffer), - "The request failed nostr authentication (error code: %d - %s)", - auth_result, nostr_strerror(auth_result)); - details = error_details_buffer; - break; - } - - send_error_response(401, error_type, message, details); - log_request("PUT", "/upload", "auth_failed", 401); - return; + free(file_data); + + // 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"; + + // Provide more specific error types based on the reason content + 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"; + } else if (strstr(result.reason, "expired")) { + error_type = "event_expired"; + message = "Authentication event expired"; + } else if (strstr(result.reason, "signature")) { + error_type = "invalid_signature"; + message = "Invalid cryptographic signature"; + } else if (strstr(result.reason, "size")) { + error_type = "file_too_large"; + message = "File size exceeds policy limits"; + } else if (strstr(result.reason, "MIME") || strstr(result.reason, "mime")) { + error_type = "unsupported_type"; + message = "File type not allowed by policy"; + } else if (strstr(result.reason, "hash")) { + error_type = "hash_blocked"; + message = "File hash blocked by policy"; + } else if (strstr(result.reason, "format") || strstr(result.reason, "invalid")) { + error_type = "invalid_format"; + message = "Invalid authorization format"; } + + send_error_response(401, error_type, message, details); + log_request("PUT", "/upload", "auth_failed", 401); + return; } // Extract uploader pubkey from validation result if auth was provided diff --git a/tests/admin_test.sh b/tests/admin_test.sh new file mode 100755 index 0000000..ba5215c --- /dev/null +++ b/tests/admin_test.sh @@ -0,0 +1,234 @@ +#!/bin/bash + +# Ginxsom Admin API Test Script +# Tests admin API endpoints using nak (for Nostr events) and curl +# +# Prerequisites: +# - nak: https://github.com/fiatjaf/nak +# - curl +# - jq (for JSON parsing) +# - Admin pubkey configured in ginxsom server_config + +set -e + +# Configuration +GINXSOM_URL="http://localhost:9001" + +# Test admin keys (for development/testing only - DO NOT USE IN PRODUCTION) +TEST_ADMIN_PRIVKEY="993bf9c54fc00bd32a5a1ce64b6d384a5fce109df1e9aee9be1052c1e5cd8120" +TEST_ADMIN_PUBKEY="2ef05348f28d24e0f0ed0751278442c27b62c823c37af8d8d89d8592c6ee84e7" + +ADMIN_PRIVKEY="${ADMIN_PRIVKEY:-${TEST_ADMIN_PRIVKEY}}" +ADMIN_PUBKEY="" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Helper functions +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +check_dependencies() { + log_info "Checking dependencies..." + + for cmd in nak curl jq; do + if ! command -v $cmd &> /dev/null; then + log_error "$cmd is not installed" + case $cmd in + nak) + echo "Install from: https://github.com/fiatjaf/nak" + ;; + jq) + echo "Install jq for JSON processing" + ;; + curl) + echo "curl should be available in most systems" + ;; + esac + exit 1 + fi + done + + log_success "All dependencies found" +} + +generate_admin_keys() { + if [[ -z "$ADMIN_PRIVKEY" ]]; then + log_info "Generating new admin key pair..." + ADMIN_PRIVKEY=$(nak key generate) + log_warning "Generated new admin private key: $ADMIN_PRIVKEY" + log_warning "Save this key for future use: export ADMIN_PRIVKEY='$ADMIN_PRIVKEY'" + fi + + ADMIN_PUBKEY=$(echo "$ADMIN_PRIVKEY" | nak key public) + log_info "Admin public key: $ADMIN_PUBKEY" +} + +create_admin_event() { + local method="$1" + local content="admin_request" + local expiration=$(($(date +%s) + 3600)) # 1 hour from now + + # Create Nostr event with nak - always use "admin" tag for admin operations + local event=$(nak event -k 24242 -c "$content" \ + --tag t="admin" \ + --tag expiration="$expiration" \ + --sec "$ADMIN_PRIVKEY") + + echo "$event" +} + +send_admin_request() { + local method="$1" + local endpoint="$2" + local data="$3" + + log_info "Testing $method $endpoint" + + # Create authenticated Nostr event + local event=$(create_admin_event "$method") + local auth_header="Nostr $(echo "$event" | base64 -w 0)" + + # Send request with curl + local curl_args=(-s -w "%{http_code}" -H "Authorization: $auth_header") + + if [[ "$method" == "PUT" && -n "$data" ]]; then + curl_args+=(-H "Content-Type: application/json" -d "$data") + fi + + local response=$(curl "${curl_args[@]}" -X "$method" "$GINXSOM_URL$endpoint") + local http_code="${response: -3}" + local body="${response%???}" + + if [[ "$http_code" =~ ^2 ]]; then + log_success "$method $endpoint - HTTP $http_code" + if [[ -n "$body" ]]; then + echo "$body" | jq . 2>/dev/null || echo "$body" + fi + else + log_error "$method $endpoint - HTTP $http_code" + echo "$body" | jq . 2>/dev/null || echo "$body" + fi + + return $([[ "$http_code" =~ ^2 ]]) +} + +test_health_endpoint() { + log_info "=== Testing Health Endpoint (no auth required) ===" + + local response=$(curl -s -w "%{http_code}" "$GINXSOM_URL/api/health") + local http_code="${response: -3}" + local body="${response%???}" + + if [[ "$http_code" =~ ^2 ]]; then + log_success "GET /api/health - HTTP $http_code" + echo "$body" | jq . + else + log_error "GET /api/health - HTTP $http_code" + echo "$body" + fi +} + +test_stats_endpoint() { + log_info "=== Testing Statistics Endpoint ===" + send_admin_request "GET" "/api/stats" +} + +test_config_endpoints() { + log_info "=== Testing Configuration Endpoints ===" + + # Get current config + send_admin_request "GET" "/api/config" + + # Update config + local config_update='{ + "max_file_size": "209715200", + "require_auth": "true", + "nip94_enabled": "true" + }' + + send_admin_request "PUT" "/api/config" "$config_update" + + # Get config again to verify + send_admin_request "GET" "/api/config" +} + +test_files_endpoint() { + log_info "=== Testing Files Endpoint ===" + send_admin_request "GET" "/api/files?limit=10&offset=0" +} + +configure_server_admin() { + log_warning "=== Server Configuration Required ===" + log_warning "To use this admin interface, add the following to your ginxsom database:" + log_warning "" + log_warning "sqlite3 db/ginxsom.db << EOF" + log_warning "INSERT OR REPLACE INTO server_config (key, value, description) VALUES" + log_warning " ('admin_pubkey', '$ADMIN_PUBKEY', 'Nostr public key authorized for admin operations')," + log_warning " ('admin_enabled', 'true', 'Enable admin interface');" + log_warning "EOF" + log_warning "" + log_warning "Then restart ginxsom server." + + echo "" + log_warning "Or use the Nak utility to interact with the API:" + echo "" + log_warning " # Create an event" + echo " EVENT=\$(nak event -k 24242 -c 'admin_request' --tag t='GET' --tag expiration=\$(date -d '+1 hour' +%s) --sec '$ADMIN_PRIVKEY')" + echo "" + log_warning " # Send authenticated request" + echo " curl -H \"Authorization: Nostr \$(echo \"\$EVENT\" | base64 -w 0)\" http://localhost:9001/api/stats" + echo "" +} + +main() { + echo "=== Ginxsom Admin API Test Suite ===" + echo "" + + check_dependencies + generate_admin_keys + + # Setup admin configuration automatically + echo "" + log_info "Setting up admin configuration..." + ./tests/init_admin.sh + echo "" + + # Test endpoints + test_health_endpoint + echo "" + + test_stats_endpoint + echo "" + + test_config_endpoints + echo "" + + test_files_endpoint + echo "" + + log_success "Admin API testing complete!" + log_info "Admin pubkey for server config: $ADMIN_PUBKEY" +} + +# Allow sourcing for individual function testing +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi \ No newline at end of file diff --git a/tests/auth_test.sh b/tests/auth_test.sh new file mode 100755 index 0000000..0c69cd2 --- /dev/null +++ b/tests/auth_test.sh @@ -0,0 +1,360 @@ +#!/bin/bash + +# auth_test.sh - Authentication System Test Suite +# Tests the unified nostr_core_lib authentication system integrated into ginxsom + +# Configuration +SERVER_URL="http://localhost:9001" +UPLOAD_ENDPOINT="${SERVER_URL}/upload" +DB_PATH="db/ginxsom.db" +TEST_DIR="tests/auth_test_tmp" + +# Test keys for different scenarios +TEST_USER1_PRIVKEY="5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a" +TEST_USER1_PUBKEY="79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + +TEST_USER2_PRIVKEY="182c3a5e3b7a1b7e4f5c6b7c8b4a5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2" +TEST_USER2_PUBKEY="c95195e5e7de1ad8c4d3c0ac4e8b5c0c4e0c4d3c1e5c8d4c2e7e9f4a5b6c7d8e" + +echo "=== Ginxsom Authentication System Test Suite ===" +echo "Testing unified nostr_core_lib authentication integration" +echo "Timestamp: $(date -Iseconds)" +echo + +# Check prerequisites +echo "[INFO] Checking prerequisites..." +for cmd in nak curl jq sqlite3; do + if ! command -v $cmd &> /dev/null; then + echo "[ERROR] $cmd command not found" + exit 1 + fi +done + +# Check if server is running +if ! curl -s -f "${SERVER_URL}/" > /dev/null 2>&1; then + echo "[ERROR] Server not running at $SERVER_URL" + echo "[INFO] Start with: ./restart-all.sh" + exit 1 +fi + +# Check if database exists +if [[ ! -f "$DB_PATH" ]]; then + echo "[ERROR] Database not found at $DB_PATH" + exit 1 +fi + +echo "[SUCCESS] All prerequisites met" +echo + +# Setup test environment and auth rules ONCE at the beginning +echo "=== Setting up authentication rules ===" +mkdir -p "$TEST_DIR" + +# Enable authentication rules +sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO auth_config (key, value) VALUES ('auth_rules_enabled', 'true');" + +# Delete ALL existing auth rules and cache (clean slate) +echo "Deleting all existing auth rules..." +sqlite3 "$DB_PATH" "DELETE FROM auth_rules;" +sqlite3 "$DB_PATH" "DELETE FROM auth_cache;" + +# Set up all test rules at once +echo "Creating test auth rules..." + +# 1. Whitelist for TEST_USER1 for upload operations (priority 10) +sqlite3 "$DB_PATH" "INSERT INTO auth_rules (rule_type, rule_target, operation, priority, enabled, description) + VALUES ('pubkey_whitelist', '$TEST_USER1_PUBKEY', 'upload', 10, 1, 'TEST_WHITELIST_USER1');" + +# 2. Blacklist for TEST_USER2 for upload operations (priority 5 - higher priority) +sqlite3 "$DB_PATH" "INSERT INTO auth_rules (rule_type, rule_target, operation, priority, enabled, description) + VALUES ('pubkey_blacklist', '$TEST_USER2_PUBKEY', 'upload', 5, 1, 'TEST_BLACKLIST_USER2');" + +# 3. Hash blacklist (will be set after we create a test file) +echo "test content for hash blacklist" > "$TEST_DIR/blacklisted_file.txt" +BLACKLISTED_HASH=$(sha256sum "$TEST_DIR/blacklisted_file.txt" | cut -d' ' -f1) +sqlite3 "$DB_PATH" "INSERT INTO auth_rules (rule_type, rule_target, operation, priority, enabled, description) + VALUES ('hash_blacklist', '$BLACKLISTED_HASH', 'upload', 5, 1, 'TEST_HASH_BLACKLIST');" + +echo "Hash blacklisted: $BLACKLISTED_HASH" + +# Display the rules we created +echo +echo "Auth rules created:" +sqlite3 "$DB_PATH" -header -column "SELECT rule_type, rule_target, operation, priority, enabled, description FROM auth_rules WHERE description LIKE 'TEST_%' ORDER BY priority;" +echo + +# Helper functions +create_test_file() { + local filename="$1" + local content="${2:-test content for $filename}" + local filepath="$TEST_DIR/$filename" + echo "$content" > "$filepath" + echo "$filepath" +} + +create_auth_event() { + local privkey="$1" + local operation="$2" + local hash="$3" + local expiration_offset="${4:-3600}" # 1 hour default + + local expiration=$(date -d "+${expiration_offset} seconds" +%s) + + local event_args=(-k 24242 -c "" --tag "t=$operation" --tag "expiration=$expiration" --sec "$privkey") + + if [[ -n "$hash" ]]; then + event_args+=(--tag "x=$hash") + fi + + nak event "${event_args[@]}" +} + +test_upload() { + local test_name="$1" + local privkey="$2" + local file_path="$3" + local expected_status="${4:-ANY}" + + echo "=== $test_name ===" + + local file_hash=$(sha256sum "$file_path" | cut -d' ' -f1) + echo "File: $(basename "$file_path")" + echo "Hash: $file_hash" + echo "User pubkey: $(echo "$privkey" | nak key public)" + + # Create auth event + local event=$(create_auth_event "$privkey" "upload" "$file_hash") + local auth_header="Nostr $(echo "$event" | base64 -w 0)" + + # Make upload request + local response_file=$(mktemp) + local http_status=$(curl -s -w "%{http_code}" \ + -H "Authorization: $auth_header" \ + -H "Content-Type: text/plain" \ + --data-binary "@$file_path" \ + -X PUT "$UPLOAD_ENDPOINT" \ + -o "$response_file") + + echo "HTTP Status: $http_status" + echo "Server Response:" + cat "$response_file" | jq . 2>/dev/null || cat "$response_file" + echo + + rm -f "$response_file" + + if [[ "$expected_status" != "ANY" ]]; then + if [[ "$http_status" == "$expected_status" ]]; then + echo "✓ Expected HTTP $expected_status - PASSED" + else + echo "✗ Expected HTTP $expected_status, got $http_status - FAILED" + fi + fi + echo +} + +# Run the tests +echo "=== Running Authentication Tests ===" +echo + +# Test 1: Whitelisted user (should succeed) +test_file1=$(create_test_file "whitelisted_upload.txt" "Content from whitelisted user") +test_upload "Test 1: Whitelisted User Upload" "$TEST_USER1_PRIVKEY" "$test_file1" "200" + +# Test 2: Blacklisted user (should fail) +test_file2=$(create_test_file "blacklisted_upload.txt" "Content from blacklisted user") +test_upload "Test 2: Blacklisted User Upload" "$TEST_USER2_PRIVKEY" "$test_file2" "403" + +# Test 3: Whitelisted user uploading blacklisted hash (blacklist should win due to higher priority) +test_upload "Test 3: Whitelisted User + Blacklisted Hash" "$TEST_USER1_PRIVKEY" "$TEST_DIR/blacklisted_file.txt" "403" + +# Test 4: Random user with no specific rules (should be allowed since no restrictive whitelist applies to all users) +test_file4=$(create_test_file "random_upload.txt" "Content from random user") +# Use a different private key that's not in any rules +RANDOM_PRIVKEY="abcd1234567890abcd1234567890abcd1234567890abcd1234567890abcd1234" +test_upload "Test 4: Random User (No Rules)" "$RANDOM_PRIVKEY" "$test_file4" "ANY" + +# Test 5: Test with authentication disabled +echo "=== Test 5: Authentication Disabled ===" +echo "Disabling authentication rules..." +sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO auth_config (key, value) VALUES ('auth_rules_enabled', 'false');" + +test_file5=$(create_test_file "auth_disabled.txt" "Upload with auth disabled") +test_upload "Test 5: Upload with Authentication Disabled" "$TEST_USER2_PRIVKEY" "$test_file5" "200" + +# Re-enable authentication +echo "Re-enabling authentication rules..." +sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO auth_config (key, value) VALUES ('auth_rules_enabled', 'true');" +echo + +# Test failure modes - comprehensive edge case testing +echo "=== Test 6: Invalid Authorization Header Formats ===" + +# Helper function for failure mode tests +test_failure_mode() { + local test_name="$1" + local auth_header="$2" + local file_content="${3:-failure_test_content}" + local expected_status="${4:-401}" + + echo "=== $test_name ===" + + local test_file=$(mktemp) + echo "$file_content" > "$test_file" + + local response_file=$(mktemp) + local http_status=$(curl -s -w "%{http_code}" \ + ${auth_header:+-H "Authorization: $auth_header"} \ + -H "Content-Type: text/plain" \ + --data-binary "@$test_file" \ + -X PUT "$UPLOAD_ENDPOINT" \ + -o "$response_file") + + echo "HTTP Status: $http_status" + echo "Server Response:" + cat "$response_file" | jq . 2>/dev/null || cat "$response_file" + echo + + rm -f "$test_file" "$response_file" + + if [[ "$http_status" == "$expected_status" ]]; then + echo "✓ Expected HTTP $expected_status - PASSED" + else + echo "✗ Expected HTTP $expected_status, got $http_status - FAILED" + fi + echo +} + +# Test 6a: Missing Authorization Header +test_failure_mode "Test 6a: Missing Authorization Header" "" + +# Test 6b: Invalid Authorization Prefix +test_failure_mode "Test 6b: Invalid Authorization Prefix" "Bearer invalidtoken123" + +# Test 6c: Invalid Base64 in Authorization +test_failure_mode "Test 6c: Invalid Base64 in Authorization" "Nostr invalid!@#base64" + +echo "=== Test 7: Malformed JSON Events ===" + +# Test 7a: Invalid JSON Structure +malformed_json='{"kind":24242,"content":"","created_at":' # Incomplete JSON +malformed_b64=$(echo -n "$malformed_json" | base64 -w 0) +test_failure_mode "Test 7a: Invalid JSON Structure" "Nostr $malformed_b64" + +# Test 7b: Missing Required Fields +missing_fields_json='{"kind":24242,"content":"","created_at":1234567890,"tags":[]}' +missing_fields_b64=$(echo -n "$missing_fields_json" | base64 -w 0) +test_failure_mode "Test 7b: Missing Required Fields (no pubkey)" "Nostr $missing_fields_b64" + +echo "=== Test 8: Invalid Key Formats ===" + +# Test 8a: Short Public Key +echo "Test 8a: Short Public Key (32 chars instead of 64)" +echo "short_key_test" > "$TEST_DIR/short_key.txt" +file_hash=$(sha256sum "$TEST_DIR/short_key.txt" | cut -d' ' -f1) +short_pubkey="1234567890abcdef1234567890abcdef" # 32 chars instead of 64 +short_key_event=$(cat << EOF +{ + "kind": 24242, + "content": "", + "created_at": $(date +%s), + "pubkey": "$short_pubkey", + "tags": [["t", "upload"], ["x", "$file_hash"]], + "id": "invalid_id", + "sig": "invalid_signature" +} +EOF +) +short_key_b64=$(echo -n "$short_key_event" | base64 -w 0) +test_failure_mode "Test 8a: Short Public Key" "Nostr $short_key_b64" + +# Test 8b: Non-hex Public Key +echo "Test 8b: Non-hex Public Key" +echo "nonhex_key_test" > "$TEST_DIR/nonhex_key.txt" +file_hash=$(sha256sum "$TEST_DIR/nonhex_key.txt" | cut -d' ' -f1) +nonhex_pubkey="gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg" # Invalid hex +nonhex_key_event=$(cat << EOF +{ + "kind": 24242, + "content": "", + "created_at": $(date +%s), + "pubkey": "$nonhex_pubkey", + "tags": [["t", "upload"], ["x", "$file_hash"]], + "id": "invalid_id", + "sig": "invalid_signature" +} +EOF +) +nonhex_key_b64=$(echo -n "$nonhex_key_event" | base64 -w 0) +test_failure_mode "Test 8b: Non-hex Public Key" "Nostr $nonhex_key_b64" + +echo "=== Test 9: Wrong Event Kind ===" + +# Test 9a: Wrong Kind (1 instead of 24242) +echo "Test 9a: Wrong Kind (kind 1 instead of 24242)" +echo "wrong_kind_test" > "$TEST_DIR/wrong_kind.txt" +file_hash=$(sha256sum "$TEST_DIR/wrong_kind.txt" | cut -d' ' -f1) +wrong_kind_event=$(nak event -k 1 -c "wrong kind test" --tag "t=upload" --tag "x=$file_hash" --sec "$TEST_USER1_PRIVKEY") +wrong_kind_b64=$(echo -n "$wrong_kind_event" | base64 -w 0) +test_failure_mode "Test 9a: Wrong Event Kind" "Nostr $wrong_kind_b64" + +echo "=== Test 10: Missing or Invalid Tags ===" + +# Test 10a: Missing 't' tag +echo "Test 10a: Missing 't' (method) tag" +echo "missing_t_tag_test" > "$TEST_DIR/missing_t_tag.txt" +file_hash=$(sha256sum "$TEST_DIR/missing_t_tag.txt" | cut -d' ' -f1) +missing_t_event=$(nak event -k 24242 -c "" --tag "x=$file_hash" --sec "$TEST_USER1_PRIVKEY") +missing_t_b64=$(echo -n "$missing_t_event" | base64 -w 0) +test_failure_mode "Test 10a: Missing 't' tag" "Nostr $missing_t_b64" + +# Test 10b: Missing 'x' tag +echo "Test 10b: Missing 'x' (hash) tag" +echo "missing_x_tag_test" > "$TEST_DIR/missing_x_tag.txt" +missing_x_event=$(nak event -k 24242 -c "" --tag "t=upload" --sec "$TEST_USER1_PRIVKEY") +missing_x_b64=$(echo -n "$missing_x_event" | base64 -w 0) +test_failure_mode "Test 10b: Missing 'x' tag" "Nostr $missing_x_b64" + +# Test 10c: Hash mismatch in 'x' tag +echo "Test 10c: Hash mismatch in 'x' tag" +echo "hash_mismatch_test" > "$TEST_DIR/hash_mismatch.txt" +wrong_hash="0000000000000000000000000000000000000000000000000000000000000000" +hash_mismatch_event=$(nak event -k 24242 -c "" --tag "t=upload" --tag "x=$wrong_hash" --sec "$TEST_USER1_PRIVKEY") +hash_mismatch_b64=$(echo -n "$hash_mismatch_event" | base64 -w 0) +test_failure_mode "Test 10c: Hash mismatch" "Nostr $hash_mismatch_b64" + +echo "=== Test 11: Expired Events ===" + +# Test 11a: Event with past expiration +echo "Test 11a: Event with past expiration" +echo "expired_event_test" > "$TEST_DIR/expired_event.txt" +file_hash=$(sha256sum "$TEST_DIR/expired_event.txt" | cut -d' ' -f1) +past_time=$(($(date +%s) - 3600)) # 1 hour ago +expired_event=$(nak event -k 24242 -c "" --tag "t=upload" --tag "x=$file_hash" --tag "expiration=$past_time" --sec "$TEST_USER1_PRIVKEY") +expired_b64=$(echo -n "$expired_event" | base64 -w 0) +test_failure_mode "Test 11a: Expired Event" "Nostr $expired_b64" + +echo "=== Test 12: Invalid Signatures ===" + +# Test 12a: Corrupted signature +echo "Test 12a: Corrupted signature" +echo "corrupted_sig_test" > "$TEST_DIR/corrupted_sig.txt" +file_hash=$(sha256sum "$TEST_DIR/corrupted_sig.txt" | cut -d' ' -f1) +valid_event=$(nak event -k 24242 -c "" --tag "t=upload" --tag "x=$file_hash" --sec "$TEST_USER1_PRIVKEY") +# Corrupt the signature by changing the last character +corrupted_event=$(echo "$valid_event" | sed 's/.\{1\}$/x/') # Replace last char with 'x' +corrupted_b64=$(echo -n "$corrupted_event" | base64 -w 0) +test_failure_mode "Test 12a: Corrupted Signature" "Nostr $corrupted_b64" + +# Show final state +echo "=== Final Database State ===" +echo "Authentication rules left in database:" +sqlite3 "$DB_PATH" -header -column "SELECT rule_type, rule_target, operation, priority, enabled, description FROM auth_rules WHERE description LIKE 'TEST_%' ORDER BY priority;" +echo +echo "Auth config:" +sqlite3 "$DB_PATH" -header -column "SELECT key, value FROM auth_config WHERE key = 'auth_rules_enabled';" +echo + +echo "=== Test Suite Completed ===" +echo "Comprehensive authentication and failure mode testing completed." +echo "Auth rules have been left in the database for inspection." +echo "To clean up, run: sqlite3 $DB_PATH \"DELETE FROM auth_rules WHERE description LIKE 'TEST_%';\"" \ No newline at end of file diff --git a/tests/auth_test_tmp/auth_disabled.txt b/tests/auth_test_tmp/auth_disabled.txt new file mode 100644 index 0000000..1e93af0 --- /dev/null +++ b/tests/auth_test_tmp/auth_disabled.txt @@ -0,0 +1 @@ +Upload with auth disabled diff --git a/tests/auth_test_tmp/blacklisted_file.txt b/tests/auth_test_tmp/blacklisted_file.txt new file mode 100644 index 0000000..2a691f6 --- /dev/null +++ b/tests/auth_test_tmp/blacklisted_file.txt @@ -0,0 +1 @@ +test content for hash blacklist diff --git a/tests/auth_test_tmp/blacklisted_upload.txt b/tests/auth_test_tmp/blacklisted_upload.txt new file mode 100644 index 0000000..d820353 --- /dev/null +++ b/tests/auth_test_tmp/blacklisted_upload.txt @@ -0,0 +1 @@ +Content from blacklisted user diff --git a/tests/auth_test_tmp/corrupted_sig.txt b/tests/auth_test_tmp/corrupted_sig.txt new file mode 100644 index 0000000..e4cadf9 --- /dev/null +++ b/tests/auth_test_tmp/corrupted_sig.txt @@ -0,0 +1 @@ +corrupted_sig_test diff --git a/tests/auth_test_tmp/expired_event.txt b/tests/auth_test_tmp/expired_event.txt new file mode 100644 index 0000000..e99cc5d --- /dev/null +++ b/tests/auth_test_tmp/expired_event.txt @@ -0,0 +1 @@ +expired_event_test diff --git a/tests/auth_test_tmp/hash_mismatch.txt b/tests/auth_test_tmp/hash_mismatch.txt new file mode 100644 index 0000000..eaf7298 --- /dev/null +++ b/tests/auth_test_tmp/hash_mismatch.txt @@ -0,0 +1 @@ +hash_mismatch_test diff --git a/tests/auth_test_tmp/missing_t_tag.txt b/tests/auth_test_tmp/missing_t_tag.txt new file mode 100644 index 0000000..bda3a0b --- /dev/null +++ b/tests/auth_test_tmp/missing_t_tag.txt @@ -0,0 +1 @@ +missing_t_tag_test diff --git a/tests/auth_test_tmp/missing_x_tag.txt b/tests/auth_test_tmp/missing_x_tag.txt new file mode 100644 index 0000000..540b87b --- /dev/null +++ b/tests/auth_test_tmp/missing_x_tag.txt @@ -0,0 +1 @@ +missing_x_tag_test diff --git a/tests/auth_test_tmp/nonhex_key.txt b/tests/auth_test_tmp/nonhex_key.txt new file mode 100644 index 0000000..71cdf00 --- /dev/null +++ b/tests/auth_test_tmp/nonhex_key.txt @@ -0,0 +1 @@ +nonhex_key_test diff --git a/tests/auth_test_tmp/random_upload.txt b/tests/auth_test_tmp/random_upload.txt new file mode 100644 index 0000000..548b50e --- /dev/null +++ b/tests/auth_test_tmp/random_upload.txt @@ -0,0 +1 @@ +Content from random user diff --git a/tests/auth_test_tmp/short_key.txt b/tests/auth_test_tmp/short_key.txt new file mode 100644 index 0000000..6b1244e --- /dev/null +++ b/tests/auth_test_tmp/short_key.txt @@ -0,0 +1 @@ +short_key_test diff --git a/tests/auth_test_tmp/whitelisted_upload.txt b/tests/auth_test_tmp/whitelisted_upload.txt new file mode 100644 index 0000000..359883a --- /dev/null +++ b/tests/auth_test_tmp/whitelisted_upload.txt @@ -0,0 +1 @@ +Content from whitelisted user diff --git a/tests/auth_test_tmp/wrong_kind.txt b/tests/auth_test_tmp/wrong_kind.txt new file mode 100644 index 0000000..bd9af4e --- /dev/null +++ b/tests/auth_test_tmp/wrong_kind.txt @@ -0,0 +1 @@ +wrong_kind_test diff --git a/tests/init_admin.sh b/tests/init_admin.sh new file mode 100755 index 0000000..b8d8149 --- /dev/null +++ b/tests/init_admin.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Admin Initialization Script for Ginxsom Testing +# Sets up the test admin key in the database + +set -e + +# Test admin public key (must match TEST_ADMIN_PUBKEY from admin_test.sh) +TEST_ADMIN_PUBKEY="2ef05348f28d24e0f0ed0751278442c27b62c823c37af8d8d89d8592c6ee84e7" + +echo "Initializing admin access for testing..." + +# Check if database exists +if [ ! -f "db/ginxsom.db" ]; then + echo "Error: Database db/ginxsom.db not found. Run ./db/init.sh first." + exit 1 +fi + +# Configure admin settings +sqlite3 db/ginxsom.db << EOF +INSERT OR REPLACE INTO server_config (key, value, description) VALUES + ('admin_pubkey', '$TEST_ADMIN_PUBKEY', 'Nostr public key authorized for admin operations (test key)'), + ('admin_enabled', 'true', 'Enable admin interface'); +EOF + +echo "Admin access configured successfully!" +echo "Test admin public key: $TEST_ADMIN_PUBKEY" +echo "Use private key from admin_test.sh to generate authentication tokens" + +# Verify configuration +echo "" +echo "Current admin configuration:" +sqlite3 db/ginxsom.db "SELECT key, value FROM server_config WHERE key IN ('admin_pubkey', 'admin_enabled');" \ No newline at end of file diff --git a/tests/simple_auth_test.sh b/tests/simple_auth_test.sh new file mode 100755 index 0000000..543ddbf --- /dev/null +++ b/tests/simple_auth_test.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Simple authentication test +set -e + +SERVER_URL="http://localhost:9001" +UPLOAD_ENDPOINT="${SERVER_URL}/upload" +TEST_USER1_PRIVKEY="5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a" + +echo "=== Simple Authentication Test ===" + +# Create a small test file +echo "Test file content $(date)" > /tmp/simple_test.txt +FILE_HASH=$(sha256sum /tmp/simple_test.txt | cut -d' ' -f1) + +echo "Test file hash: $FILE_HASH" + +# Create auth event +EVENT=$(nak event -k 24242 -c "" \ + --tag "t=upload" \ + --tag "x=${FILE_HASH}" \ + --tag "expiration=$(date -d '+1 hour' +%s)" \ + --sec "$TEST_USER1_PRIVKEY") + +echo "Generated event: $EVENT" + +# Create auth header +AUTH_HEADER="Nostr $(echo "$EVENT" | base64 -w 0)" + +echo "Auth header length: ${#AUTH_HEADER}" + +# Test upload +echo "Testing upload..." +HTTP_STATUS=$(curl -s -w "%{http_code}" \ + -H "Authorization: $AUTH_HEADER" \ + -H "Content-Type: text/plain" \ + --data-binary "@/tmp/simple_test.txt" \ + -X PUT "$UPLOAD_ENDPOINT" \ + -o /tmp/upload_response.txt) + +echo "HTTP Status: $HTTP_STATUS" +echo "Response:" +cat /tmp/upload_response.txt +echo + +# Cleanup +rm -f /tmp/simple_test.txt /tmp/upload_response.txt + +echo "Test completed with status: $HTTP_STATUS" \ No newline at end of file diff --git a/tests/simple_comprehensive_test.sh b/tests/simple_comprehensive_test.sh new file mode 100755 index 0000000..9b4f2f5 --- /dev/null +++ b/tests/simple_comprehensive_test.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# Simple comprehensive auth test +SERVER_URL="http://localhost:9001" +UPLOAD_ENDPOINT="${SERVER_URL}/upload" +DB_PATH="../db/ginxsom.db" + +# Test keys +TEST_USER1_PRIVKEY="5c0c523f52a5b6fad39ed2403092df8cebc36318b39383bca6c00808626fab3a" +TEST_USER1_PUBKEY="79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + +echo "=== Simple Authentication Test ===" + +# Test 1: Basic upload +echo "Test 1: Basic upload" +echo "test content" > test1.txt +file_hash=$(sha256sum test1.txt | cut -d" " -f1) + +# Create auth event +event=$(nak event -k 24242 -c "" --tag "t=upload" --tag "expiration=$(date -d "+1 hour" +%s)" --tag "x=$file_hash" --sec "$TEST_USER1_PRIVKEY") +auth_header="Nostr $(echo "$event" | base64 -w 0)" + +# Make upload request +response=$(curl -s -w "%{http_code}" -H "Authorization: $auth_header" -H "Content-Type: text/plain" --data-binary "@test1.txt" -X PUT "$UPLOAD_ENDPOINT" -o response1.json) + +if [ "$response" = "200" ]; then + echo "✓ Basic upload test PASSED (HTTP $response)" +else + echo "✗ Basic upload test FAILED (HTTP $response)" + cat response1.json +fi + +# Test 2: Whitelist rule +echo +echo "Test 2: Pubkey whitelist" + +# Clear rules and add whitelist +sqlite3 "$DB_PATH" "DELETE FROM auth_rules WHERE description LIKE %TEST_%;" +sqlite3 "$DB_PATH" "DELETE FROM auth_cache;" +sqlite3 "$DB_PATH" "INSERT INTO auth_rules (rule_type, rule_target, operation, priority, enabled, description) VALUES (pubkey_whitelist, , upload, 10, 1, TEST_WHITELIST);" + +echo "test content 2" > test2.txt +file_hash2=$(sha256sum test2.txt | cut -d" " -f1) + +event2=$(nak event -k 24242 -c "" --tag "t=upload" --tag "expiration=$(date -d "+1 hour" +%s)" --tag "x=$file_hash2" --sec "$TEST_USER1_PRIVKEY") +auth_header2="Nostr $(echo "$event2" | base64 -w 0)" + +response2=$(curl -s -w "%{http_code}" -H "Authorization: $auth_header2" -H "Content-Type: text/plain" --data-binary "@test2.txt" -X PUT "$UPLOAD_ENDPOINT" -o response2.json) + +if [ "$response2" = "200" ]; then + echo "✓ Whitelist test PASSED (HTTP $response2)" +else + echo "✗ Whitelist test FAILED (HTTP $response2)" + cat response2.json +fi + +# Cleanup +rm -f test1.txt test2.txt response1.json response2.json +sqlite3 "$DB_PATH" "DELETE FROM auth_rules WHERE description LIKE %TEST_%;" +sqlite3 "$DB_PATH" "DELETE FROM auth_cache;" + +echo "=== Tests completed ===" diff --git a/tests/tests/auth_test_tmp/auth_disabled.txt b/tests/tests/auth_test_tmp/auth_disabled.txt new file mode 100644 index 0000000..1e93af0 --- /dev/null +++ b/tests/tests/auth_test_tmp/auth_disabled.txt @@ -0,0 +1 @@ +Upload with auth disabled diff --git a/tests/tests/auth_test_tmp/blacklisted_file.txt b/tests/tests/auth_test_tmp/blacklisted_file.txt new file mode 100644 index 0000000..2a691f6 --- /dev/null +++ b/tests/tests/auth_test_tmp/blacklisted_file.txt @@ -0,0 +1 @@ +test content for hash blacklist diff --git a/tests/tests/auth_test_tmp/blacklisted_upload.txt b/tests/tests/auth_test_tmp/blacklisted_upload.txt new file mode 100644 index 0000000..d820353 --- /dev/null +++ b/tests/tests/auth_test_tmp/blacklisted_upload.txt @@ -0,0 +1 @@ +Content from blacklisted user diff --git a/tests/tests/auth_test_tmp/corrupted_sig.txt b/tests/tests/auth_test_tmp/corrupted_sig.txt new file mode 100644 index 0000000..e4cadf9 --- /dev/null +++ b/tests/tests/auth_test_tmp/corrupted_sig.txt @@ -0,0 +1 @@ +corrupted_sig_test diff --git a/tests/tests/auth_test_tmp/expired_event.txt b/tests/tests/auth_test_tmp/expired_event.txt new file mode 100644 index 0000000..e99cc5d --- /dev/null +++ b/tests/tests/auth_test_tmp/expired_event.txt @@ -0,0 +1 @@ +expired_event_test diff --git a/tests/tests/auth_test_tmp/hash_mismatch.txt b/tests/tests/auth_test_tmp/hash_mismatch.txt new file mode 100644 index 0000000..eaf7298 --- /dev/null +++ b/tests/tests/auth_test_tmp/hash_mismatch.txt @@ -0,0 +1 @@ +hash_mismatch_test diff --git a/tests/tests/auth_test_tmp/missing_t_tag.txt b/tests/tests/auth_test_tmp/missing_t_tag.txt new file mode 100644 index 0000000..bda3a0b --- /dev/null +++ b/tests/tests/auth_test_tmp/missing_t_tag.txt @@ -0,0 +1 @@ +missing_t_tag_test diff --git a/tests/tests/auth_test_tmp/missing_x_tag.txt b/tests/tests/auth_test_tmp/missing_x_tag.txt new file mode 100644 index 0000000..540b87b --- /dev/null +++ b/tests/tests/auth_test_tmp/missing_x_tag.txt @@ -0,0 +1 @@ +missing_x_tag_test diff --git a/tests/tests/auth_test_tmp/nonhex_key.txt b/tests/tests/auth_test_tmp/nonhex_key.txt new file mode 100644 index 0000000..71cdf00 --- /dev/null +++ b/tests/tests/auth_test_tmp/nonhex_key.txt @@ -0,0 +1 @@ +nonhex_key_test diff --git a/tests/tests/auth_test_tmp/random_upload.txt b/tests/tests/auth_test_tmp/random_upload.txt new file mode 100644 index 0000000..548b50e --- /dev/null +++ b/tests/tests/auth_test_tmp/random_upload.txt @@ -0,0 +1 @@ +Content from random user diff --git a/tests/tests/auth_test_tmp/short_key.txt b/tests/tests/auth_test_tmp/short_key.txt new file mode 100644 index 0000000..6b1244e --- /dev/null +++ b/tests/tests/auth_test_tmp/short_key.txt @@ -0,0 +1 @@ +short_key_test diff --git a/tests/tests/auth_test_tmp/whitelisted_upload.txt b/tests/tests/auth_test_tmp/whitelisted_upload.txt new file mode 100644 index 0000000..359883a --- /dev/null +++ b/tests/tests/auth_test_tmp/whitelisted_upload.txt @@ -0,0 +1 @@ +Content from whitelisted user diff --git a/tests/tests/auth_test_tmp/wrong_kind.txt b/tests/tests/auth_test_tmp/wrong_kind.txt new file mode 100644 index 0000000..bd9af4e --- /dev/null +++ b/tests/tests/auth_test_tmp/wrong_kind.txt @@ -0,0 +1 @@ +wrong_kind_test