Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00d16f8615 | ||
|
|
c90676d2b2 | ||
|
|
b89c011ad5 | ||
|
|
c3de31aa88 | ||
|
|
b6df0be865 | ||
|
|
a89f84f76e | ||
|
|
5a916cc221 | ||
|
|
dcf421ff93 | ||
|
|
d655258311 |
17
Makefile
17
Makefile
@@ -197,4 +197,21 @@ help:
|
||||
@echo " make init-db # Set up database"
|
||||
@echo " make force-version # Force regenerate main.h from git"
|
||||
|
||||
# Build fully static MUSL binaries using Docker
|
||||
static-musl-x86_64:
|
||||
@echo "Building fully static MUSL binary for x86_64..."
|
||||
docker buildx build --platform linux/amd64 -f examples/deployment/static-builder.Dockerfile -t c-relay-static-builder-x86_64 --load .
|
||||
docker run --rm -v $(PWD)/build:/output c-relay-static-builder-x86_64 sh -c "cp /c_relay_static_musl_x86_64 /output/"
|
||||
@echo "Static binary created: build/c_relay_static_musl_x86_64"
|
||||
|
||||
static-musl-arm64:
|
||||
@echo "Building fully static MUSL binary for ARM64..."
|
||||
docker buildx build --platform linux/arm64 -f examples/deployment/static-builder.Dockerfile -t c-relay-static-builder-arm64 --load .
|
||||
docker run --rm -v $(PWD)/build:/output c-relay-static-builder-arm64 sh -c "cp /c_relay_static_musl_x86_64 /output/c_relay_static_musl_arm64"
|
||||
@echo "Static binary created: build/c_relay_static_musl_arm64"
|
||||
|
||||
static-musl: static-musl-x86_64 static-musl-arm64
|
||||
@echo "Built static MUSL binaries for both architectures"
|
||||
|
||||
.PHONY: static-musl-x86_64 static-musl-arm64 static-musl
|
||||
.PHONY: all x86 arm64 test init-db clean clean-all install-deps install-cross-tools install-arm64-deps check-toolchain help force-version
|
||||
68
README.md
68
README.md
@@ -1,6 +1,6 @@
|
||||
# C-Nostr Relay
|
||||
|
||||
A high-performance Nostr relay implemented in C with SQLite backend, featuring a revolutionary **zero-configuration** approach using event-based configuration management.
|
||||
A high-performance Nostr relay implemented in C with SQLite backend, featuring nostr event-based management.
|
||||
|
||||
## Supported NIPs
|
||||
|
||||
@@ -22,6 +22,69 @@ Do NOT modify the formatting, add emojis, or change the text. Keep the simple fo
|
||||
- [x] NIP-50: Keywords filter
|
||||
- [x] NIP-70: Protected Events
|
||||
|
||||
## Quick Start
|
||||
|
||||
Get your C-Relay up and running in minutes with a static binary (no dependencies required):
|
||||
|
||||
### 1. Download Static Binary
|
||||
|
||||
Download the latest static release from the [releases page](https://git.laantungir.net/laantungir/c-relay/releases):
|
||||
|
||||
```bash
|
||||
# Static binary - works on all Linux distributions (no dependencies)
|
||||
wget https://git.laantungir.net/laantungir/c-relay/releases/download/v0.6.0/c-relay-v0.6.0-linux-x86_64-static
|
||||
chmod +x c-relay-v0.6.0-linux-x86_64-static
|
||||
mv c-relay-v0.6.0-linux-x86_64-static c-relay
|
||||
```
|
||||
|
||||
### 2. Start the Relay
|
||||
|
||||
Simply run the binary - no configuration files needed:
|
||||
|
||||
```bash
|
||||
./c-relay
|
||||
```
|
||||
|
||||
On first startup, you'll see:
|
||||
- **Admin Private Key**: Save this securely! You'll need it for administration
|
||||
- **Relay Public Key**: Your relay's identity on the Nostr network
|
||||
- **Port Information**: Default is 8888, or the next available port
|
||||
|
||||
### 3. Access the Web Interface
|
||||
|
||||
Open your browser and navigate to:
|
||||
```
|
||||
http://localhost:8888/api/
|
||||
```
|
||||
|
||||
The web interface provides:
|
||||
- Real-time configuration management
|
||||
- Database statistics dashboard
|
||||
- Auth rules management
|
||||
- Secure admin authentication with your Nostr identity
|
||||
|
||||
### 4. Test Your Relay
|
||||
|
||||
Test basic connectivity:
|
||||
```bash
|
||||
# Test WebSocket connection
|
||||
curl -H "Accept: application/nostr+json" http://localhost:8888
|
||||
|
||||
# Test with a Nostr client
|
||||
# Add ws://localhost:8888 to your client's relay list
|
||||
```
|
||||
|
||||
### 5. Configure Your Relay (Optional)
|
||||
|
||||
Use the web interface or send admin commands to customize:
|
||||
- Relay name and description
|
||||
- Authentication rules (whitelist/blacklist)
|
||||
- Connection limits
|
||||
- Proof-of-work requirements
|
||||
|
||||
**That's it!** Your relay is now running with zero configuration required. The event-based configuration system means you can adjust all settings through the web interface or admin API without editing config files.
|
||||
|
||||
|
||||
## Web Admin Interface
|
||||
|
||||
C-Relay includes a **built-in web-based administration interface** accessible at `http://localhost:8888/api/`. The interface provides:
|
||||
@@ -34,6 +97,7 @@ C-Relay includes a **built-in web-based administration interface** accessible at
|
||||
|
||||
The web interface serves embedded static files with no external dependencies and includes proper CORS headers for browser compatibility.
|
||||
|
||||
|
||||
## Administrator API
|
||||
|
||||
C-Relay uses an innovative **event-based administration system** where all configuration and management commands are sent as signed Nostr events using the admin private key generated during first startup. All admin commands use **NIP-44 encrypted command arrays** for security and compatibility.
|
||||
@@ -269,7 +333,7 @@ All admin commands return **signed EVENT responses** via WebSocket following sta
|
||||
|
||||
In addition to the above admin API, c-relay allows the administrator to direct message the relay to get information or control some settings. As long as the administrator is signed in with any nostr client that allows sending nip-17 direct messages (DMs), they can control the relay.
|
||||
|
||||
The is possible because the relay is a full nostr citizen with it's own private and public key.
|
||||
The is possible because the relay is a full nostr citizen with it's own private and public key, and it knows the administrator's public key.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<div class="inline-buttons">
|
||||
<button type="button" id="connect-relay-btn">CONNECT TO RELAY</button>
|
||||
<button type="button" id="disconnect-relay-btn" disabled>DISCONNECT</button>
|
||||
<button type="button" id="test-websocket-btn" disabled>TEST WEBSOCKET</button>
|
||||
<button type="button" id="restart-relay-btn" disabled>RESTART RELAY</button>
|
||||
</div>
|
||||
|
||||
<div class="status disconnected" id="relay-connection-status">NOT CONNECTED</div>
|
||||
|
||||
120
api/index.js
120
api/index.js
@@ -41,7 +41,7 @@
|
||||
const relayConnectionStatus = document.getElementById('relay-connection-status');
|
||||
const connectRelayBtn = document.getElementById('connect-relay-btn');
|
||||
const disconnectRelayBtn = document.getElementById('disconnect-relay-btn');
|
||||
const testWebSocketBtn = document.getElementById('test-websocket-btn');
|
||||
const restartRelayBtn = document.getElementById('restart-relay-btn');
|
||||
const configDisplay = document.getElementById('config-display');
|
||||
const configTableBody = document.getElementById('config-table-body');
|
||||
|
||||
@@ -369,28 +369,28 @@
|
||||
relayConnectionStatus.className = 'status connected';
|
||||
connectRelayBtn.disabled = true;
|
||||
disconnectRelayBtn.disabled = true;
|
||||
testWebSocketBtn.disabled = true;
|
||||
restartRelayBtn.disabled = true;
|
||||
break;
|
||||
case 'connected':
|
||||
relayConnectionStatus.textContent = 'CONNECTED';
|
||||
relayConnectionStatus.className = 'status connected';
|
||||
connectRelayBtn.disabled = true;
|
||||
disconnectRelayBtn.disabled = false;
|
||||
testWebSocketBtn.disabled = false;
|
||||
restartRelayBtn.disabled = false;
|
||||
break;
|
||||
case 'disconnected':
|
||||
relayConnectionStatus.textContent = 'NOT CONNECTED';
|
||||
relayConnectionStatus.className = 'status disconnected';
|
||||
connectRelayBtn.disabled = false;
|
||||
disconnectRelayBtn.disabled = true;
|
||||
testWebSocketBtn.disabled = true;
|
||||
restartRelayBtn.disabled = true;
|
||||
break;
|
||||
case 'error':
|
||||
relayConnectionStatus.textContent = 'CONNECTION FAILED';
|
||||
relayConnectionStatus.className = 'status error';
|
||||
connectRelayBtn.disabled = false;
|
||||
disconnectRelayBtn.disabled = true;
|
||||
testWebSocketBtn.disabled = true;
|
||||
restartRelayBtn.disabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1670,22 +1670,12 @@
|
||||
disconnectFromRelay();
|
||||
});
|
||||
|
||||
testWebSocketBtn.addEventListener('click', function (e) {
|
||||
restartRelayBtn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const url = relayConnectionUrl.value.trim();
|
||||
if (!url) {
|
||||
log('Please enter a relay URL first', 'ERROR');
|
||||
return;
|
||||
}
|
||||
|
||||
testWebSocketConnection(url)
|
||||
.then(() => {
|
||||
log('WebSocket test successful', 'INFO');
|
||||
})
|
||||
.catch(error => {
|
||||
log(`WebSocket test failed: ${error.message}`, 'ERROR');
|
||||
});
|
||||
sendRestartCommand().catch(error => {
|
||||
log(`Restart command failed: ${error.message}`, 'ERROR');
|
||||
});
|
||||
});
|
||||
|
||||
// ================================
|
||||
@@ -3078,10 +3068,14 @@
|
||||
messageDiv.className = 'log-entry';
|
||||
|
||||
const directionColor = direction === 'sent' ? '#007bff' : '#28a745';
|
||||
|
||||
// Convert newlines to <br> tags for proper HTML display
|
||||
const formattedMessage = message.replace(/\n/g, '<br>');
|
||||
|
||||
messageDiv.innerHTML = `
|
||||
<span class="log-timestamp">${timestamp}</span>
|
||||
<span style="color: ${directionColor}; font-weight: bold;">[${direction.toUpperCase()}]</span>
|
||||
${message}
|
||||
<span style="white-space: pre-wrap;">${formattedMessage}</span>
|
||||
`;
|
||||
|
||||
// Remove the "No messages received yet" placeholder if it exists
|
||||
@@ -3144,6 +3138,83 @@
|
||||
// DATABASE STATISTICS FUNCTIONS
|
||||
// ================================
|
||||
|
||||
// Send restart command to restart the relay using Administrator API
|
||||
async function sendRestartCommand() {
|
||||
if (!isLoggedIn || !userPubkey) {
|
||||
log('Must be logged in to restart relay', 'ERROR');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!relayPool) {
|
||||
log('SimplePool connection not available', 'ERROR');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
log('Sending restart command to relay...', 'INFO');
|
||||
|
||||
// Create command array for restart
|
||||
const command_array = ["system_command", "restart"];
|
||||
|
||||
// Encrypt the command array directly using NIP-44
|
||||
const encrypted_content = await encryptForRelay(JSON.stringify(command_array));
|
||||
if (!encrypted_content) {
|
||||
throw new Error('Failed to encrypt command array');
|
||||
}
|
||||
|
||||
// Create single kind 23456 admin event
|
||||
const restartEvent = {
|
||||
kind: 23456,
|
||||
pubkey: userPubkey,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [["p", getRelayPubkey()]],
|
||||
content: encrypted_content
|
||||
};
|
||||
|
||||
// Sign the event
|
||||
const signedEvent = await window.nostr.signEvent(restartEvent);
|
||||
if (!signedEvent || !signedEvent.sig) {
|
||||
throw new Error('Event signing failed');
|
||||
}
|
||||
|
||||
// Publish via SimplePool
|
||||
const url = relayConnectionUrl.value.trim();
|
||||
const publishPromises = relayPool.publish([url], signedEvent);
|
||||
|
||||
// Use Promise.allSettled to capture per-relay outcomes
|
||||
const results = await Promise.allSettled(publishPromises);
|
||||
|
||||
// Check if any relay accepted the event
|
||||
let successCount = 0;
|
||||
results.forEach((result, index) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
successCount++;
|
||||
log(`Restart command published successfully to relay ${index}`, 'INFO');
|
||||
} else {
|
||||
log(`Restart command failed on relay ${index}: ${result.reason?.message || result.reason}`, 'ERROR');
|
||||
}
|
||||
});
|
||||
|
||||
if (successCount === 0) {
|
||||
const errorDetails = results.map((r, i) => `Relay ${i}: ${r.reason?.message || r.reason}`).join('; ');
|
||||
throw new Error(`All relays rejected restart command. Details: ${errorDetails}`);
|
||||
}
|
||||
|
||||
log('Restart command sent successfully - relay should restart shortly...', 'INFO');
|
||||
|
||||
// Update connection status to indicate restart is in progress
|
||||
updateRelayConnectionStatus('connecting');
|
||||
relayConnectionStatus.textContent = 'RESTARTING...';
|
||||
|
||||
// The relay will disconnect and need to be reconnected after restart
|
||||
// This will be handled by the WebSocket disconnection event
|
||||
|
||||
} catch (error) {
|
||||
log(`Failed to send restart command: ${error.message}`, 'ERROR');
|
||||
updateRelayConnectionStatus('error');
|
||||
}
|
||||
}
|
||||
|
||||
// Send stats_query command to get database statistics using Administrator API (inner events)
|
||||
async function sendStatsQuery() {
|
||||
if (!isLoggedIn || !userPubkey) {
|
||||
@@ -3300,9 +3371,12 @@
|
||||
const events7d = document.getElementById('events-7d');
|
||||
const events30d = document.getElementById('events-30d');
|
||||
|
||||
if (events24h) events24h.textContent = data.events_24h || '-';
|
||||
if (events7d) events7d.textContent = data.events_7d || '-';
|
||||
if (events30d) events30d.textContent = data.events_30d || '-';
|
||||
// Access the nested time_stats object from backend response
|
||||
const timeStats = data.time_stats || {};
|
||||
|
||||
if (events24h) events24h.textContent = timeStats.last_24h || '0';
|
||||
if (events7d) events7d.textContent = timeStats.last_7d || '0';
|
||||
if (events30d) events30d.textContent = timeStats.last_30d || '0';
|
||||
}
|
||||
|
||||
// Populate top pubkeys table
|
||||
|
||||
@@ -17,6 +17,33 @@ print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
COMMIT_MESSAGE=""
|
||||
RELEASE_MODE=false
|
||||
|
||||
show_usage() {
|
||||
echo "C-Relay Build and Push Script"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " $0 \"commit message\" - Default: compile, increment patch, commit & push"
|
||||
echo " $0 -r \"commit message\" - Release: compile x86+arm64, increment minor, create release"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 \"Fixed event validation bug\""
|
||||
echo " $0 --release \"Major release with new features\""
|
||||
echo ""
|
||||
echo "Default Mode (patch increment):"
|
||||
echo " - Compile C-Relay"
|
||||
echo " - Increment patch version (v1.2.3 → v1.2.4)"
|
||||
echo " - Git add, commit with message, and push"
|
||||
echo ""
|
||||
echo "Release Mode (-r flag):"
|
||||
echo " - Compile C-Relay for x86_64 and arm64 (dynamic and static versions)"
|
||||
echo " - Increment minor version, zero patch (v1.2.3 → v1.3.0)"
|
||||
echo " - Git add, commit, push, and create Gitea release"
|
||||
echo ""
|
||||
echo "Requirements for Release Mode:"
|
||||
echo " - For ARM64 builds: make install-arm64-deps (optional - will build x86_64 only if missing)"
|
||||
echo " - For static builds: sudo apt-get install musl-dev libcap-dev libuv1-dev libev-dev"
|
||||
echo " - Gitea token in ~/.gitea_token for release uploads"
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
@@ -38,32 +65,6 @@ while [[ $# -gt 0 ]]; do
|
||||
esac
|
||||
done
|
||||
|
||||
show_usage() {
|
||||
echo "C-Relay Build and Push Script"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " $0 \"commit message\" - Default: compile, increment patch, commit & push"
|
||||
echo " $0 -r \"commit message\" - Release: compile x86+arm64, increment minor, create release"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 \"Fixed event validation bug\""
|
||||
echo " $0 --release \"Major release with new features\""
|
||||
echo ""
|
||||
echo "Default Mode (patch increment):"
|
||||
echo " - Compile C-Relay"
|
||||
echo " - Increment patch version (v1.2.3 → v1.2.4)"
|
||||
echo " - Git add, commit with message, and push"
|
||||
echo ""
|
||||
echo "Release Mode (-r flag):"
|
||||
echo " - Compile C-Relay for x86_64 and arm64"
|
||||
echo " - Increment minor version, zero patch (v1.2.3 → v1.3.0)"
|
||||
echo " - Git add, commit, push, and create Gitea release"
|
||||
echo ""
|
||||
echo "Requirements for Release Mode:"
|
||||
echo " - For ARM64 builds: make install-arm64-deps (optional - will build x86_64 only if missing)"
|
||||
echo " - Gitea token in ~/.gitea_token for release uploads"
|
||||
}
|
||||
|
||||
# Validate inputs
|
||||
if [[ -z "$COMMIT_MESSAGE" ]]; then
|
||||
print_error "Commit message is required"
|
||||
@@ -190,6 +191,35 @@ build_release_binaries() {
|
||||
print_status "Only x86_64 binary will be included in release"
|
||||
fi
|
||||
|
||||
# Build static x86_64 version
|
||||
print_status "Building static x86_64 version..."
|
||||
make clean > /dev/null 2>&1
|
||||
if make static-musl-x86_64 > /dev/null 2>&1; then
|
||||
if [[ -f "build/c_relay_static_musl_x86_64" ]]; then
|
||||
cp build/c_relay_static_musl_x86_64 c-relay-static-x86_64
|
||||
print_success "Static x86_64 binary created: c-relay-static-x86_64"
|
||||
else
|
||||
print_warning "Static x86_64 binary not found after compilation"
|
||||
fi
|
||||
else
|
||||
print_warning "Static x86_64 build failed - MUSL development packages may not be installed"
|
||||
print_status "Run 'sudo apt-get install musl-dev libcap-dev libuv1-dev libev-dev' to enable static builds"
|
||||
fi
|
||||
|
||||
# Try to build static ARM64 version
|
||||
print_status "Attempting static ARM64 build..."
|
||||
make clean > /dev/null 2>&1
|
||||
if make static-musl-arm64 > /dev/null 2>&1; then
|
||||
if [[ -f "build/c_relay_static_musl_arm64" ]]; then
|
||||
cp build/c_relay_static_musl_arm64 c-relay-static-arm64
|
||||
print_success "Static ARM64 binary created: c-relay-static-arm64"
|
||||
else
|
||||
print_warning "Static ARM64 binary not found after compilation"
|
||||
fi
|
||||
else
|
||||
print_warning "Static ARM64 build failed - ARM64 cross-compilation or MUSL ARM64 packages not set up"
|
||||
fi
|
||||
|
||||
# Restore normal build
|
||||
make clean > /dev/null 2>&1
|
||||
make > /dev/null 2>&1
|
||||
@@ -319,12 +349,18 @@ create_gitea_release() {
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\": \"$NEW_VERSION\", \"name\": \"$NEW_VERSION\", \"body\": \"$COMMIT_MESSAGE\"}")
|
||||
|
||||
local upload_result=false
|
||||
|
||||
if echo "$response" | grep -q '"id"'; then
|
||||
print_success "Created release $NEW_VERSION"
|
||||
upload_release_binaries "$api_url" "$token"
|
||||
if upload_release_binaries "$api_url" "$token"; then
|
||||
upload_result=true
|
||||
fi
|
||||
elif echo "$response" | grep -q "already exists"; then
|
||||
print_warning "Release $NEW_VERSION already exists"
|
||||
upload_release_binaries "$api_url" "$token"
|
||||
if upload_release_binaries "$api_url" "$token"; then
|
||||
upload_result=true
|
||||
fi
|
||||
else
|
||||
print_error "Failed to create release $NEW_VERSION"
|
||||
print_error "Response: $response"
|
||||
@@ -334,18 +370,29 @@ create_gitea_release() {
|
||||
local check_response=$(curl -s -H "Authorization: token $token" "$api_url/releases/tags/$NEW_VERSION")
|
||||
if echo "$check_response" | grep -q '"id"'; then
|
||||
print_warning "Release exists but creation response was unexpected"
|
||||
upload_release_binaries "$api_url" "$token"
|
||||
if upload_release_binaries "$api_url" "$token"; then
|
||||
upload_result=true
|
||||
fi
|
||||
else
|
||||
print_error "Release does not exist and creation failed"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Return based on upload success
|
||||
if [[ "$upload_result" == true ]]; then
|
||||
return 0
|
||||
else
|
||||
print_error "Binary upload failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to upload release binaries
|
||||
upload_release_binaries() {
|
||||
local api_url="$1"
|
||||
local token="$2"
|
||||
local upload_success=true
|
||||
|
||||
# Get release ID with more robust parsing
|
||||
print_status "Getting release ID for $NEW_VERSION..."
|
||||
@@ -367,37 +414,131 @@ upload_release_binaries() {
|
||||
# Upload x86_64 binary
|
||||
if [[ -f "c-relay-x86_64" ]]; then
|
||||
print_status "Uploading x86_64 binary..."
|
||||
if curl -s -X POST "$api_url/releases/$release_id/assets" \
|
||||
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
|
||||
-H "Authorization: token $token" \
|
||||
-F "attachment=@c-relay-x86_64;filename=c-relay-${NEW_VERSION}-linux-x86_64" > /dev/null; then
|
||||
print_success "Uploaded x86_64 binary"
|
||||
-F "attachment=@c-relay-x86_64;filename=c-relay-${NEW_VERSION}-linux-x86_64")
|
||||
|
||||
local http_code=$(echo "$upload_response" | tail -n1)
|
||||
local response_body=$(echo "$upload_response" | head -n -1)
|
||||
|
||||
if [[ "$http_code" == "201" ]]; then
|
||||
print_success "Uploaded x86_64 binary successfully"
|
||||
else
|
||||
print_warning "Failed to upload x86_64 binary"
|
||||
print_error "Failed to upload x86_64 binary (HTTP $http_code)"
|
||||
print_error "Response: $response_body"
|
||||
upload_success=false
|
||||
fi
|
||||
else
|
||||
print_warning "x86_64 binary not found: c-relay-x86_64"
|
||||
fi
|
||||
|
||||
# Upload ARM64 binary
|
||||
if [[ -f "c-relay-arm64" ]]; then
|
||||
print_status "Uploading ARM64 binary..."
|
||||
if curl -s -X POST "$api_url/releases/$release_id/assets" \
|
||||
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
|
||||
-H "Authorization: token $token" \
|
||||
-F "attachment=@c-relay-arm64;filename=c-relay-${NEW_VERSION}-linux-arm64" > /dev/null; then
|
||||
print_success "Uploaded ARM64 binary"
|
||||
-F "attachment=@c-relay-arm64;filename=c-relay-${NEW_VERSION}-linux-arm64")
|
||||
|
||||
local http_code=$(echo "$upload_response" | tail -n1)
|
||||
local response_body=$(echo "$upload_response" | head -n -1)
|
||||
|
||||
if [[ "$http_code" == "201" ]]; then
|
||||
print_success "Uploaded ARM64 binary successfully"
|
||||
else
|
||||
print_warning "Failed to upload ARM64 binary"
|
||||
print_error "Failed to upload ARM64 binary (HTTP $http_code)"
|
||||
print_error "Response: $response_body"
|
||||
upload_success=false
|
||||
fi
|
||||
else
|
||||
print_warning "ARM64 binary not found: c-relay-arm64"
|
||||
fi
|
||||
|
||||
# Upload static x86_64 binary
|
||||
if [[ -f "c-relay-static-x86_64" ]]; then
|
||||
print_status "Uploading static x86_64 binary..."
|
||||
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
|
||||
-H "Authorization: token $token" \
|
||||
-F "attachment=@c-relay-static-x86_64;filename=c-relay-${NEW_VERSION}-linux-x86_64-static")
|
||||
|
||||
local http_code=$(echo "$upload_response" | tail -n1)
|
||||
local response_body=$(echo "$upload_response" | head -n -1)
|
||||
|
||||
if [[ "$http_code" == "201" ]]; then
|
||||
print_success "Uploaded static x86_64 binary successfully"
|
||||
else
|
||||
print_error "Failed to upload static x86_64 binary (HTTP $http_code)"
|
||||
print_error "Response: $response_body"
|
||||
upload_success=false
|
||||
fi
|
||||
else
|
||||
print_warning "Static x86_64 binary not found: c-relay-static-x86_64"
|
||||
fi
|
||||
|
||||
# Upload static ARM64 binary
|
||||
if [[ -f "c-relay-static-arm64" ]]; then
|
||||
print_status "Uploading static ARM64 binary..."
|
||||
local upload_response=$(curl -s -w "\n%{http_code}" -X POST "$api_url/releases/$release_id/assets" \
|
||||
-H "Authorization: token $token" \
|
||||
-F "attachment=@c-relay-static-arm64;filename=c-relay-${NEW_VERSION}-linux-arm64-static")
|
||||
|
||||
local http_code=$(echo "$upload_response" | tail -n1)
|
||||
local response_body=$(echo "$upload_response" | head -n -1)
|
||||
|
||||
if [[ "$http_code" == "201" ]]; then
|
||||
print_success "Uploaded static ARM64 binary successfully"
|
||||
else
|
||||
print_error "Failed to upload static ARM64 binary (HTTP $http_code)"
|
||||
print_error "Response: $response_body"
|
||||
upload_success=false
|
||||
fi
|
||||
else
|
||||
print_warning "Static ARM64 binary not found: c-relay-static-arm64"
|
||||
fi
|
||||
|
||||
# Return success/failure status
|
||||
if [[ "$upload_success" == true ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to clean up release binaries
|
||||
cleanup_release_binaries() {
|
||||
if [[ -f "c-relay-x86_64" ]]; then
|
||||
rm -f c-relay-x86_64
|
||||
print_status "Cleaned up x86_64 binary"
|
||||
fi
|
||||
if [[ -f "c-relay-arm64" ]]; then
|
||||
rm -f c-relay-arm64
|
||||
print_status "Cleaned up ARM64 binary"
|
||||
local force_cleanup="$1" # Optional parameter to force cleanup even on failure
|
||||
|
||||
if [[ "$force_cleanup" == "force" ]] || [[ "$upload_success" == true ]]; then
|
||||
if [[ -f "c-relay-x86_64" ]]; then
|
||||
rm -f c-relay-x86_64
|
||||
print_status "Cleaned up x86_64 binary"
|
||||
fi
|
||||
if [[ -f "c-relay-arm64" ]]; then
|
||||
rm -f c-relay-arm64
|
||||
print_status "Cleaned up ARM64 binary"
|
||||
fi
|
||||
if [[ -f "c-relay-static-x86_64" ]]; then
|
||||
rm -f c-relay-static-x86_64
|
||||
print_status "Cleaned up static x86_64 binary"
|
||||
fi
|
||||
if [[ -f "c-relay-static-arm64" ]]; then
|
||||
rm -f c-relay-static-arm64
|
||||
print_status "Cleaned up static ARM64 binary"
|
||||
fi
|
||||
else
|
||||
print_warning "Keeping binary files due to upload failures"
|
||||
print_status "Files available for manual upload:"
|
||||
if [[ -f "c-relay-x86_64" ]]; then
|
||||
print_status " - c-relay-x86_64"
|
||||
fi
|
||||
if [[ -f "c-relay-arm64" ]]; then
|
||||
print_status " - c-relay-arm64"
|
||||
fi
|
||||
if [[ -f "c-relay-static-x86_64" ]]; then
|
||||
print_status " - c-relay-static-x86_64"
|
||||
fi
|
||||
if [[ -f "c-relay-static-arm64" ]]; then
|
||||
print_status " - c-relay-static-arm64"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -433,14 +574,18 @@ main() {
|
||||
git_commit_and_push_no_tag
|
||||
|
||||
# Create Gitea release with binaries
|
||||
create_gitea_release
|
||||
if create_gitea_release; then
|
||||
print_success "Release $NEW_VERSION completed successfully!"
|
||||
print_status "Binaries uploaded to Gitea release"
|
||||
upload_success=true
|
||||
else
|
||||
print_error "Release creation or binary upload failed"
|
||||
upload_success=false
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
# Cleanup (only if upload was successful)
|
||||
cleanup_release_binaries
|
||||
|
||||
print_success "Release $NEW_VERSION completed successfully!"
|
||||
print_status "Binaries uploaded to Gitea release"
|
||||
|
||||
else
|
||||
print_status "=== DEFAULT MODE ==="
|
||||
|
||||
|
||||
144
build_static.sh
Executable file
144
build_static.sh
Executable file
@@ -0,0 +1,144 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Build fully static MUSL binaries for C-Relay
|
||||
# Produces portable binaries with zero runtime dependencies
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BUILD_DIR="$SCRIPT_DIR/build"
|
||||
|
||||
echo "Building fully static MUSL binaries for C-Relay..."
|
||||
echo "Project directory: $SCRIPT_DIR"
|
||||
echo "Build directory: $BUILD_DIR"
|
||||
|
||||
# Create build directory
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
# Check if Docker is available first
|
||||
if command -v docker &> /dev/null && sudo docker buildx version &> /dev/null 2>&1; then
|
||||
echo "Docker available but Alpine repositories are having issues - using native build"
|
||||
USE_DOCKER=false
|
||||
else
|
||||
echo "Docker not available - attempting native MUSL build"
|
||||
USE_DOCKER=false
|
||||
fi
|
||||
|
||||
# Check if musl-gcc is available for native build
|
||||
if [ "$USE_DOCKER" = false ]; then
|
||||
if ! command -v musl-gcc &> /dev/null; then
|
||||
echo "Installing musl development tools..."
|
||||
sudo apt update && sudo apt install -y musl-dev musl-tools
|
||||
|
||||
if ! command -v musl-gcc &> /dev/null; then
|
||||
echo "ERROR: Failed to install musl-gcc"
|
||||
echo "Please install musl-dev package manually: sudo apt install musl-dev musl-tools"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$USE_DOCKER" = true ]; then
|
||||
# Docker-based build
|
||||
echo "Building x86_64 static binary with Docker..."
|
||||
sudo docker buildx build \
|
||||
--platform linux/amd64 \
|
||||
-f "$SCRIPT_DIR/examples/deployment/static-builder.Dockerfile" \
|
||||
-t c-relay-static-builder-x86_64 \
|
||||
--load \
|
||||
"$SCRIPT_DIR"
|
||||
|
||||
# Extract x86_64 binary
|
||||
sudo docker run --rm -v "$BUILD_DIR:/output" c-relay-static-builder-x86_64 \
|
||||
sh -c "cp /c_relay_static_musl_x86_64 /output/c_relay_static_x86_64"
|
||||
|
||||
echo "x86_64 static binary created: $BUILD_DIR/c_relay_static_x86_64"
|
||||
|
||||
# Build ARM64 static binary
|
||||
echo "Building ARM64 static binary with Docker..."
|
||||
sudo docker buildx build \
|
||||
--platform linux/arm64 \
|
||||
-f "$SCRIPT_DIR/examples/deployment/static-builder.Dockerfile" \
|
||||
-t c-relay-static-builder-arm64 \
|
||||
--load \
|
||||
"$SCRIPT_DIR"
|
||||
|
||||
# Extract ARM64 binary
|
||||
sudo docker run --rm -v "$BUILD_DIR:/output" c-relay-static-builder-arm64 \
|
||||
sh -c "cp /c_relay_static_musl_arm64 /output/c_relay_static_arm64"
|
||||
|
||||
echo "ARM64 static binary created: $BUILD_DIR/c_relay_static_arm64"
|
||||
else
|
||||
# Native static build with regular gcc
|
||||
echo "Building static binary with gcc..."
|
||||
|
||||
# Check for required static libraries
|
||||
echo "Checking for static libraries..."
|
||||
MISSING_LIBS=""
|
||||
|
||||
for lib in libsqlite3.a libssl.a libcrypto.a libz.a; do
|
||||
if ! find /usr/lib* /usr/local/lib* -name "$lib" 2>/dev/null | head -1 | grep -q .; then
|
||||
MISSING_LIBS="$MISSING_LIBS $lib"
|
||||
fi
|
||||
done
|
||||
|
||||
# libsecp256k1 might not be available as static lib, so we'll try without it first
|
||||
|
||||
# Initialize submodules if needed
|
||||
if [ ! -f "nostr_core_lib/libnostr_core_x64.a" ]; then
|
||||
echo "Building nostr_core_lib..."
|
||||
git submodule update --init --recursive
|
||||
cd nostr_core_lib && ./build.sh && cd ..
|
||||
fi
|
||||
|
||||
# Install additional static libraries needed for libwebsockets
|
||||
echo "Installing additional static libraries..."
|
||||
sudo apt install -y libcap-dev libuv1-dev libev-dev
|
||||
|
||||
# Try building with regular gcc and static linking
|
||||
echo "Compiling with gcc -static..."
|
||||
|
||||
# Use the same approach as the regular Makefile but with static linking
|
||||
gcc -static -O2 -Wall -Wextra -std=c99 -g \
|
||||
-I. -Inostr_core_lib -Inostr_core_lib/nostr_core -Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \
|
||||
src/main.c src/config.c src/dm_admin.c src/request_validator.c src/nip009.c src/nip011.c src/nip013.c src/nip040.c src/nip042.c src/websockets.c src/subscriptions.c src/api.c src/embedded_web_content.c \
|
||||
-o "$BUILD_DIR/c_relay_static_x86_64" \
|
||||
nostr_core_lib/libnostr_core_x64.a \
|
||||
-lsqlite3 -lwebsockets -lz -ldl -lpthread -lm -L/usr/local/lib -lsecp256k1 -lssl -lcrypto -L/usr/local/lib -lcurl -lcap -luv_a -lev
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "x86_64 static binary created: $BUILD_DIR/c_relay_static_x86_64"
|
||||
else
|
||||
echo "ERROR: Static build failed"
|
||||
echo "This may be due to missing static libraries or incompatible library versions"
|
||||
echo "Consider using Docker-based build instead"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify binaries
|
||||
echo "Verifying static binaries..."
|
||||
for binary in "$BUILD_DIR"/c_relay_static_*; do
|
||||
if [ -f "$binary" ]; then
|
||||
echo "Binary: $(basename "$binary")"
|
||||
file "$binary"
|
||||
ls -lh "$binary"
|
||||
|
||||
# Test if binary is truly static (no dynamic dependencies)
|
||||
if ldd "$binary" 2>/dev/null | grep -q "not a dynamic executable"; then
|
||||
echo "✓ Binary is fully static"
|
||||
elif ldd "$binary" 2>/dev/null | grep -q "statically linked"; then
|
||||
echo "✓ Binary is statically linked"
|
||||
else
|
||||
echo "⚠ Binary may have dynamic dependencies:"
|
||||
ldd "$binary" 2>/dev/null || echo " (ldd check failed)"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Static build complete!"
|
||||
echo "Binaries available in: $BUILD_DIR/"
|
||||
ls -la "$BUILD_DIR"/c_relay_static_* 2>/dev/null || echo "No static binaries found"
|
||||
echo ""
|
||||
echo "These binaries should have minimal runtime dependencies and work across Linux distributions."
|
||||
136
examples/deployment/static-builder.Dockerfile
Normal file
136
examples/deployment/static-builder.Dockerfile
Normal file
@@ -0,0 +1,136 @@
|
||||
# MUSL-based fully static C-Relay builder
|
||||
# Produces portable binaries with zero runtime dependencies
|
||||
|
||||
FROM alpine:latest AS builder
|
||||
|
||||
# Add alternative mirrors and install build dependencies with retry
|
||||
RUN echo "http://dl-cdn.alpinelinux.org/alpine/v3.22/main" > /etc/apk/repositories && \
|
||||
echo "http://dl-cdn.alpinelinux.org/alpine/v3.22/community" >> /etc/apk/repositories && \
|
||||
echo "http://mirror.leaseweb.com/alpine/v3.22/main" >> /etc/apk/repositories && \
|
||||
echo "http://mirror.leaseweb.com/alpine/v3.22/community" >> /etc/apk/repositories && \
|
||||
apk update --no-cache || (sleep 5 && apk update --no-cache) || (sleep 10 && apk update --no-cache)
|
||||
|
||||
# Install build dependencies with retry logic
|
||||
RUN apk add --no-cache \
|
||||
build-base \
|
||||
musl-dev \
|
||||
git \
|
||||
cmake \
|
||||
pkgconfig \
|
||||
autoconf \
|
||||
automake \
|
||||
libtool \
|
||||
openssl-dev \
|
||||
openssl-libs-static \
|
||||
zlib-dev \
|
||||
zlib-static \
|
||||
curl-dev \
|
||||
curl-static \
|
||||
sqlite-dev \
|
||||
sqlite-static \
|
||||
linux-headers || \
|
||||
(sleep 10 && apk add --no-cache \
|
||||
build-base \
|
||||
musl-dev \
|
||||
git \
|
||||
cmake \
|
||||
pkgconfig \
|
||||
autoconf \
|
||||
automake \
|
||||
libtool \
|
||||
openssl-dev \
|
||||
openssl-libs-static \
|
||||
zlib-dev \
|
||||
zlib-static \
|
||||
curl-dev \
|
||||
curl-static \
|
||||
sqlite-dev \
|
||||
sqlite-static \
|
||||
linux-headers)
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /build
|
||||
|
||||
# Build zlib static (if needed)
|
||||
RUN if [ ! -f /usr/lib/libz.a ]; then \
|
||||
cd /tmp && \
|
||||
wget https://zlib.net/zlib-1.3.1.tar.gz && \
|
||||
tar xzf zlib-1.3.1.tar.gz && \
|
||||
cd zlib-1.3.1 && \
|
||||
./configure --static --prefix=/usr && \
|
||||
make && make install; \
|
||||
fi
|
||||
|
||||
# Build OpenSSL static
|
||||
RUN cd /tmp && \
|
||||
wget https://www.openssl.org/source/openssl-3.0.13.tar.gz && \
|
||||
tar xzf openssl-3.0.13.tar.gz && \
|
||||
cd openssl-3.0.13 && \
|
||||
./Configure linux-x86_64 no-shared --prefix=/usr && \
|
||||
make && make install_sw
|
||||
|
||||
# Build libsecp256k1 static
|
||||
RUN cd /tmp && \
|
||||
git clone https://github.com/bitcoin-core/secp256k1.git && \
|
||||
cd secp256k1 && \
|
||||
./autogen.sh && \
|
||||
./configure --enable-static --disable-shared --prefix=/usr && \
|
||||
make && make install
|
||||
|
||||
# Build libwebsockets static with OpenSSL
|
||||
RUN cd /tmp && \
|
||||
git clone https://github.com/warmcat/libwebsockets.git && \
|
||||
cd libwebsockets && \
|
||||
mkdir build && cd build && \
|
||||
cmake .. \
|
||||
-DLWS_WITH_STATIC=ON \
|
||||
-DLWS_WITH_SHARED=OFF \
|
||||
-DLWS_WITH_SSL=ON \
|
||||
-DLWS_OPENSSL_LIBRARIES="/usr/lib/libssl.a;/usr/lib/libcrypto.a" \
|
||||
-DLWS_OPENSSL_INCLUDE_DIRS="/usr/include" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr && \
|
||||
make && make install
|
||||
|
||||
# Build curl static (minimal features)
|
||||
RUN cd /tmp && \
|
||||
wget https://curl.se/download/curl-8.6.0.tar.gz && \
|
||||
tar xzf curl-8.6.0.tar.gz && \
|
||||
cd curl-8.6.0 && \
|
||||
./configure \
|
||||
--disable-shared \
|
||||
--enable-static \
|
||||
--disable-ldap \
|
||||
--without-libidn2 \
|
||||
--without-brotli \
|
||||
--without-zstd \
|
||||
--without-rtmp \
|
||||
--without-libpsl \
|
||||
--without-krb5 \
|
||||
--with-openssl \
|
||||
--prefix=/usr && \
|
||||
make && make install
|
||||
|
||||
# Copy c-relay source
|
||||
COPY . /build/
|
||||
|
||||
# Initialize submodules
|
||||
RUN git submodule update --init --recursive
|
||||
|
||||
# Build nostr_core_lib
|
||||
RUN cd nostr_core_lib && ./build.sh
|
||||
|
||||
# Build c-relay static
|
||||
RUN make clean && \
|
||||
CC="musl-gcc -static" \
|
||||
CFLAGS="-O2 -Wall -Wextra -std=c99 -g" \
|
||||
LDFLAGS="-static -Wl,--whole-archive -lpthread -Wl,--no-whole-archive" \
|
||||
LIBS="-lsqlite3 -lwebsockets -lz -ldl -lpthread -lm -lsecp256k1 -lssl -lcrypto -lcurl" \
|
||||
make
|
||||
|
||||
# Strip binary for size
|
||||
RUN strip build/c_relay_x86
|
||||
|
||||
# Multi-stage build to produce minimal output
|
||||
FROM scratch AS output
|
||||
COPY --from=builder /build/build/c_relay_x86 /c_relay_static_musl_x86_64
|
||||
@@ -23,8 +23,6 @@ int store_event(cJSON* event);
|
||||
|
||||
// Handle HTTP request for embedded files (assumes GET)
|
||||
int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
|
||||
log_info("Handling embedded file request");
|
||||
|
||||
const char* file_path;
|
||||
|
||||
// Handle /api requests
|
||||
@@ -124,7 +122,6 @@ int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
|
||||
// Request callback for body transmission
|
||||
lws_callback_on_writable(wsi);
|
||||
|
||||
log_success("Embedded file headers sent, body transmission scheduled");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -165,6 +162,5 @@ int handle_embedded_file_writeable(struct lws* wsi) {
|
||||
free(session_data);
|
||||
lws_set_wsi_user(wsi, NULL);
|
||||
|
||||
log_success("Embedded file served successfully");
|
||||
return 0;
|
||||
}
|
||||
|
||||
323
src/config.c
323
src/config.c
@@ -16,6 +16,9 @@
|
||||
// External database connection (from main.c)
|
||||
extern sqlite3* g_db;
|
||||
|
||||
// External shutdown flag (from main.c)
|
||||
extern volatile sig_atomic_t g_shutdown_flag;
|
||||
|
||||
// Global unified configuration cache instance
|
||||
unified_config_cache_t g_unified_cache = {
|
||||
.cache_lock = PTHREAD_MUTEX_INITIALIZER,
|
||||
@@ -223,24 +226,18 @@ static int refresh_unified_cache_from_table(void) {
|
||||
log_info("Refreshing unified configuration cache from database");
|
||||
|
||||
// Lock the cache for update (don't memset entire cache to avoid wiping relay_info)
|
||||
log_info("DEBUG: Acquiring cache lock for refresh");
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
log_info("DEBUG: Cache lock acquired");
|
||||
|
||||
// Load critical config values from table
|
||||
log_info("DEBUG: Loading admin_pubkey from table");
|
||||
const char* admin_pubkey = get_config_value_from_table("admin_pubkey");
|
||||
if (admin_pubkey) {
|
||||
log_info("DEBUG: Setting admin_pubkey in cache");
|
||||
strncpy(g_unified_cache.admin_pubkey, admin_pubkey, sizeof(g_unified_cache.admin_pubkey) - 1);
|
||||
g_unified_cache.admin_pubkey[sizeof(g_unified_cache.admin_pubkey) - 1] = '\0';
|
||||
free((char*)admin_pubkey);
|
||||
}
|
||||
|
||||
log_info("DEBUG: Loading relay_pubkey from table");
|
||||
const char* relay_pubkey = get_config_value_from_table("relay_pubkey");
|
||||
if (relay_pubkey) {
|
||||
log_info("DEBUG: Setting relay_pubkey in cache");
|
||||
strncpy(g_unified_cache.relay_pubkey, relay_pubkey, sizeof(g_unified_cache.relay_pubkey) - 1);
|
||||
g_unified_cache.relay_pubkey[sizeof(g_unified_cache.relay_pubkey) - 1] = '\0';
|
||||
free((char*)relay_pubkey);
|
||||
@@ -358,12 +355,10 @@ static int refresh_unified_cache_from_table(void) {
|
||||
}
|
||||
|
||||
// Set cache expiration
|
||||
log_info("DEBUG: Setting cache expiration and validity");
|
||||
int cache_timeout = get_cache_timeout();
|
||||
g_unified_cache.cache_expires = time(NULL) + cache_timeout;
|
||||
g_unified_cache.cache_valid = 1;
|
||||
|
||||
log_info("DEBUG: Releasing cache lock");
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
log_info("Unified configuration cache refreshed from database");
|
||||
@@ -1796,9 +1791,7 @@ int process_configuration_event(const cJSON* event) {
|
||||
log_error("Invalid configuration event");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing configuration event...");
|
||||
|
||||
|
||||
// Validate event structure
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
@@ -2259,60 +2252,38 @@ extern int is_authorized_admin_event(cJSON* event);
|
||||
|
||||
// Process admin events (updated for Kind 23456)
|
||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
log_info("DEBUG: Entering process_admin_event_in_config()");
|
||||
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
if (!kind_obj || !cJSON_IsNumber(kind_obj)) {
|
||||
log_error("DEBUG: Missing or invalid kind in admin event");
|
||||
log_error("Missing or invalid kind in admin event");
|
||||
snprintf(error_message, error_size, "invalid: missing or invalid kind");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int kind = (int)cJSON_GetNumberValue(kind_obj);
|
||||
log_info("DEBUG: Processing admin event");
|
||||
printf(" Event kind: %d\n", kind);
|
||||
|
||||
// Extract and log event details for debugging
|
||||
|
||||
// Get event pubkey for authorization logging
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
cJSON* content_obj = cJSON_GetObjectItem(event, "content");
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
|
||||
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : "unknown";
|
||||
const char* event_content = content_obj ? cJSON_GetStringValue(content_obj) : "unknown";
|
||||
|
||||
log_info("DEBUG: Event details");
|
||||
printf(" Pubkey: %.16s...\n", event_pubkey ? event_pubkey : "null");
|
||||
printf(" Content length: %zu\n", event_content ? strlen(event_content) : 0);
|
||||
printf(" Has tags: %s\n", tags_obj ? "yes" : "no");
|
||||
if (tags_obj && cJSON_IsArray(tags_obj)) {
|
||||
printf(" Tags count: %d\n", cJSON_GetArraySize(tags_obj));
|
||||
}
|
||||
|
||||
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
||||
|
||||
// DEFENSE-IN-DEPTH: Use comprehensive admin authorization validation
|
||||
log_info("DEBUG: Checking admin authorization");
|
||||
if (!is_authorized_admin_event(event)) {
|
||||
// Log the unauthorized attempt for security monitoring
|
||||
char log_msg[256];
|
||||
snprintf(log_msg, sizeof(log_msg),
|
||||
"DEBUG: Unauthorized admin event attempt in config processing - pubkey: %.16s...",
|
||||
"Unauthorized admin event attempt in config processing - pubkey: %.16s...",
|
||||
event_pubkey ? event_pubkey : "null");
|
||||
log_warning(log_msg);
|
||||
|
||||
|
||||
snprintf(error_message, error_size, "auth-required: not authorized admin");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Log successful admin authorization for audit trail
|
||||
log_info("DEBUG: Admin event authorized successfully in config processing");
|
||||
|
||||
|
||||
// Route to appropriate handler based on kind
|
||||
log_info("DEBUG: Routing to kind-specific handler");
|
||||
switch (kind) {
|
||||
case 23456: // New ephemeral auth rules management
|
||||
log_info("DEBUG: Routing to process_admin_auth_event (kind 23456)");
|
||||
return process_admin_auth_event(event, error_message, error_size, wsi);
|
||||
default:
|
||||
log_error("DEBUG: Unsupported admin event kind");
|
||||
log_error("Unsupported admin event kind");
|
||||
printf(" Unsupported kind: %d\n", kind);
|
||||
snprintf(error_message, error_size, "invalid: unsupported admin event kind %d", kind);
|
||||
return -1;
|
||||
@@ -2323,10 +2294,7 @@ int process_admin_event_in_config(cJSON* event, char* error_message, size_t erro
|
||||
int process_admin_config_event(cJSON* event, char* error_message, size_t error_size) {
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
int kind = kind_obj ? (int)cJSON_GetNumberValue(kind_obj) : 0;
|
||||
|
||||
log_info("Processing admin configuration event");
|
||||
printf(" Kind: %d\n", kind);
|
||||
|
||||
|
||||
// Parse tags to find query commands according to API specification
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
if (tags_obj && cJSON_IsArray(tags_obj)) {
|
||||
@@ -2419,21 +2387,15 @@ int process_admin_config_event(cJSON* event, char* error_message, size_t error_s
|
||||
|
||||
// Handle Kind 23456 auth rules management
|
||||
int process_admin_auth_event(cJSON* event, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
log_info("DEBUG: Entering process_admin_auth_event()");
|
||||
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
int kind = kind_obj ? (int)cJSON_GetNumberValue(kind_obj) : 0;
|
||||
|
||||
log_info("DEBUG: Processing admin auth rule event through unified handler");
|
||||
printf(" Kind: %d\n", kind);
|
||||
|
||||
|
||||
// Extract and log additional event details for debugging
|
||||
cJSON* content_obj = cJSON_GetObjectItem(event, "content");
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
|
||||
|
||||
const char* event_content = content_obj ? cJSON_GetStringValue(content_obj) : "unknown";
|
||||
|
||||
log_info("DEBUG: Auth event details");
|
||||
|
||||
printf(" Content length: %zu\n", event_content ? strlen(event_content) : 0);
|
||||
printf(" Has tags: %s\n", tags_obj ? "yes" : "no");
|
||||
if (tags_obj && cJSON_IsArray(tags_obj)) {
|
||||
@@ -2442,12 +2404,10 @@ int process_admin_auth_event(cJSON* event, char* error_message, size_t error_siz
|
||||
|
||||
// Route all Kind 23456 events through the unified handler
|
||||
if (kind == 23456) {
|
||||
log_info("DEBUG: Routing Kind 23456 to unified handler");
|
||||
return handle_kind_23456_unified(event, error_message, error_size, wsi);
|
||||
}
|
||||
|
||||
|
||||
log_error("DEBUG: Unsupported auth event kind in process_admin_auth_event");
|
||||
|
||||
|
||||
printf(" Unsupported kind: %d\n", kind);
|
||||
snprintf(error_message, error_size, "invalid: unsupported auth event kind %d", kind);
|
||||
return -1;
|
||||
@@ -2854,118 +2814,86 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
||||
// Suppress unused parameter warning
|
||||
(void)wsi;
|
||||
if (!event) {
|
||||
log_error("DEBUG: Null event passed to handle_kind_23456_unified");
|
||||
log_error("invalid: null event");
|
||||
snprintf(error_message, error_size, "invalid: null event");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("DEBUG: Processing Kind 23456 event through unified handler");
|
||||
|
||||
// Check if content is encrypted (NIP-44)
|
||||
cJSON* content_obj = cJSON_GetObjectItem(event, "content");
|
||||
if (!content_obj || !cJSON_IsString(content_obj)) {
|
||||
log_error("DEBUG: Missing or invalid content in Kind 23456 event");
|
||||
log_error("invalid: missing or invalid content");
|
||||
snprintf(error_message, error_size, "invalid: missing or invalid content");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
const char* content = cJSON_GetStringValue(content_obj);
|
||||
log_info("DEBUG: Event content analysis");
|
||||
printf(" Content length: %zu\n", content ? strlen(content) : 0);
|
||||
printf(" Content preview: %.50s%s\n", content ? content : "null",
|
||||
(content && strlen(content) > 50) ? "..." : "");
|
||||
|
||||
cJSON* decrypted_content = NULL;
|
||||
|
||||
// Check if content looks like NIP-44 encrypted content (base64 string, not JSON)
|
||||
if (content && strlen(content) > 10 && content[0] != '[' && content[0] != '{') {
|
||||
log_info("DEBUG: Detected NIP-44 encrypted content, attempting decryption");
|
||||
printf(" Content appears to be base64 encrypted\n");
|
||||
|
||||
// Get relay private key for decryption
|
||||
log_info("DEBUG: Retrieving relay private key for decryption");
|
||||
char* relay_privkey = get_relay_private_key();
|
||||
if (!relay_privkey) {
|
||||
log_error("DEBUG: Relay private key not available for decryption");
|
||||
log_error("error: relay private key not available for decryption");
|
||||
snprintf(error_message, error_size, "error: relay private key not available for decryption");
|
||||
return -1;
|
||||
}
|
||||
log_info("DEBUG: Relay private key retrieved successfully");
|
||||
printf(" Relay privkey length: %zu\n", strlen(relay_privkey));
|
||||
|
||||
// Get sender's pubkey from the event for NIP-44 decryption
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
if (!pubkey_obj || !cJSON_IsString(pubkey_obj)) {
|
||||
log_error("DEBUG: Missing sender pubkey in event");
|
||||
log_error("invalid: missing sender pubkey in event");
|
||||
free(relay_privkey);
|
||||
snprintf(error_message, error_size, "invalid: missing sender pubkey in event");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
const char* sender_pubkey = cJSON_GetStringValue(pubkey_obj);
|
||||
if (!sender_pubkey || strlen(sender_pubkey) != 64) {
|
||||
log_error("DEBUG: Invalid sender pubkey format");
|
||||
printf(" Sender pubkey: %s\n", sender_pubkey ? sender_pubkey : "null");
|
||||
printf(" Sender pubkey length: %zu\n", sender_pubkey ? strlen(sender_pubkey) : 0);
|
||||
log_error("invalid: invalid sender pubkey format");
|
||||
free(relay_privkey);
|
||||
snprintf(error_message, error_size, "invalid: invalid sender pubkey format");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("DEBUG: Sender pubkey validated");
|
||||
printf(" Sender pubkey: %.16s...\n", sender_pubkey);
|
||||
|
||||
// Convert relay private key from hex to bytes
|
||||
log_info("DEBUG: Converting relay private key from hex to bytes");
|
||||
unsigned char relay_privkey_bytes[32];
|
||||
if (nostr_hex_to_bytes(relay_privkey, relay_privkey_bytes, 32) != NOSTR_SUCCESS) {
|
||||
log_error("DEBUG: Failed to convert relay private key from hex");
|
||||
log_error("error: failed to convert relay private key");
|
||||
free(relay_privkey);
|
||||
snprintf(error_message, error_size, "error: failed to convert relay private key");
|
||||
return -1;
|
||||
}
|
||||
log_info("DEBUG: Relay private key converted successfully");
|
||||
|
||||
|
||||
// Convert sender public key from hex to bytes
|
||||
log_info("DEBUG: Converting sender public key from hex to bytes");
|
||||
unsigned char sender_pubkey_bytes[32];
|
||||
if (nostr_hex_to_bytes(sender_pubkey, sender_pubkey_bytes, 32) != NOSTR_SUCCESS) {
|
||||
log_error("DEBUG: Failed to convert sender public key from hex");
|
||||
log_error("error: failed to convert sender public key");
|
||||
free(relay_privkey);
|
||||
snprintf(error_message, error_size, "error: failed to convert sender public key");
|
||||
return -1;
|
||||
}
|
||||
log_info("DEBUG: Sender public key converted successfully");
|
||||
|
||||
|
||||
// Perform NIP-44 decryption (relay as recipient, admin as sender)
|
||||
log_info("DEBUG: Performing NIP-44 decryption");
|
||||
printf(" Encrypted content length: %zu\n", strlen(content));
|
||||
char decrypted_text[4096]; // Buffer for decrypted content
|
||||
int decrypt_result = nostr_nip44_decrypt(relay_privkey_bytes, sender_pubkey_bytes, content, decrypted_text, sizeof(decrypted_text));
|
||||
|
||||
|
||||
// Clean up private key immediately after use
|
||||
memset(relay_privkey_bytes, 0, 32);
|
||||
free(relay_privkey);
|
||||
|
||||
|
||||
if (decrypt_result != NOSTR_SUCCESS) {
|
||||
log_error("DEBUG: NIP-44 decryption failed");
|
||||
printf(" Decryption result code: %d\n", decrypt_result);
|
||||
log_error("error: NIP-44 decryption failed");
|
||||
snprintf(error_message, error_size, "error: NIP-44 decryption failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("DEBUG: NIP-44 decryption successful");
|
||||
printf(" Decrypted content: %s\n", decrypted_text);
|
||||
printf(" Decrypted length: %zu\n", strlen(decrypted_text));
|
||||
|
||||
// Check if decrypted content is a direct command array (DM control system)
|
||||
log_info("DEBUG: Checking if decrypted content is direct command array");
|
||||
cJSON* potential_command_array = cJSON_Parse(decrypted_text);
|
||||
|
||||
if (potential_command_array && cJSON_IsArray(potential_command_array)) {
|
||||
log_info("DEBUG: Detected direct command array - routing to DM admin system");
|
||||
printf(" Direct command array detected, size: %d\n", cJSON_GetArraySize(potential_command_array));
|
||||
|
||||
// Route to DM admin system
|
||||
int dm_result = process_dm_admin_command(potential_command_array, event, error_message, error_size, wsi);
|
||||
cJSON_Delete(potential_command_array);
|
||||
@@ -2974,74 +2902,51 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
||||
}
|
||||
|
||||
// If not a direct command array, try parsing as inner event JSON (NIP-17)
|
||||
log_info("DEBUG: Not a direct command array, parsing as inner event JSON");
|
||||
cJSON* inner_event = potential_command_array; // Reuse the parsed JSON
|
||||
|
||||
if (!inner_event || !cJSON_IsObject(inner_event)) {
|
||||
log_error("DEBUG: Decrypted content is not valid inner event JSON");
|
||||
printf(" Decrypted content type: %s\n",
|
||||
inner_event ? (cJSON_IsObject(inner_event) ? "object" : "other") : "null");
|
||||
log_error("error: decrypted content is not valid inner event JSON");
|
||||
cJSON_Delete(inner_event);
|
||||
snprintf(error_message, error_size, "error: decrypted content is not valid inner event JSON");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("DEBUG: Inner event parsed successfully");
|
||||
printf(" Inner event kind: %d\n", (int)cJSON_GetNumberValue(cJSON_GetObjectItem(inner_event, "kind")));
|
||||
|
||||
// Extract content from inner event
|
||||
cJSON* inner_content_obj = cJSON_GetObjectItem(inner_event, "content");
|
||||
if (!inner_content_obj || !cJSON_IsString(inner_content_obj)) {
|
||||
log_error("DEBUG: Inner event missing content field");
|
||||
log_error("error: inner event missing content field");
|
||||
cJSON_Delete(inner_event);
|
||||
snprintf(error_message, error_size, "error: inner event missing content field");
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* inner_content = cJSON_GetStringValue(inner_content_obj);
|
||||
log_info("DEBUG: Extracted inner content");
|
||||
printf(" Inner content: %s\n", inner_content);
|
||||
|
||||
// Parse inner content as JSON array (the command array)
|
||||
log_info("DEBUG: Parsing inner content as command JSON array");
|
||||
decrypted_content = cJSON_Parse(inner_content);
|
||||
|
||||
if (!decrypted_content || !cJSON_IsArray(decrypted_content)) {
|
||||
log_error("DEBUG: Inner content is not valid JSON array");
|
||||
printf(" Inner content type: %s\n",
|
||||
decrypted_content ? (cJSON_IsArray(decrypted_content) ? "array" : "other") : "null");
|
||||
log_error("error: inner content is not valid JSON array");
|
||||
cJSON_Delete(inner_event);
|
||||
snprintf(error_message, error_size, "error: inner content is not valid JSON array");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("DEBUG: Inner content parsed successfully as JSON array");
|
||||
printf(" Array size: %d\n", cJSON_GetArraySize(decrypted_content));
|
||||
|
||||
// Clean up inner event
|
||||
cJSON_Delete(inner_event);
|
||||
|
||||
|
||||
// Replace event content with decrypted command array for processing
|
||||
log_info("DEBUG: Replacing event content with decrypted marker");
|
||||
cJSON_DeleteItemFromObject(event, "content");
|
||||
cJSON_AddStringToObject(event, "content", "decrypted");
|
||||
|
||||
// Create synthetic tags from decrypted command array
|
||||
log_info("DEBUG: Creating synthetic tags from decrypted command array");
|
||||
printf(" Decrypted content array size: %d\n", cJSON_GetArraySize(decrypted_content));
|
||||
|
||||
// Create synthetic tags from decrypted command array
|
||||
// Create new tags array with command tag first
|
||||
cJSON* new_tags = cJSON_CreateArray();
|
||||
|
||||
// Add decrypted command as first tag
|
||||
if (cJSON_GetArraySize(decrypted_content) > 0) {
|
||||
log_info("DEBUG: Adding decrypted command as synthetic tag");
|
||||
cJSON* first_item = cJSON_GetArrayItem(decrypted_content, 0);
|
||||
if (cJSON_IsString(first_item)) {
|
||||
const char* command_name = cJSON_GetStringValue(first_item);
|
||||
log_info("DEBUG: Creating command tag");
|
||||
printf(" Command: %s\n", command_name ? command_name : "null");
|
||||
|
||||
cJSON* command_tag = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(command_tag, cJSON_Duplicate(first_item, 1));
|
||||
|
||||
@@ -3049,139 +2954,91 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
||||
for (int i = 1; i < cJSON_GetArraySize(decrypted_content); i++) {
|
||||
cJSON* item = cJSON_GetArrayItem(decrypted_content, i);
|
||||
if (item) {
|
||||
if (cJSON_IsString(item)) {
|
||||
printf(" Arg %d: %s\n", i, cJSON_GetStringValue(item));
|
||||
} else {
|
||||
printf(" Arg %d: (non-string)\n", i);
|
||||
}
|
||||
cJSON_AddItemToArray(command_tag, cJSON_Duplicate(item, 1));
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(new_tags, command_tag);
|
||||
log_info("DEBUG: Synthetic command tag added to new tags array");
|
||||
printf(" New tags after adding command: %d\n", cJSON_GetArraySize(new_tags));
|
||||
} else {
|
||||
log_error("DEBUG: First item in decrypted array is not a string");
|
||||
log_error("error: first item in decrypted array is not a string");
|
||||
}
|
||||
} else {
|
||||
log_error("DEBUG: Decrypted array is empty");
|
||||
}
|
||||
|
||||
// Add existing tags
|
||||
cJSON* existing_tags = cJSON_GetObjectItem(event, "tags");
|
||||
if (existing_tags && cJSON_IsArray(existing_tags)) {
|
||||
printf(" Existing tags count: %d\n", cJSON_GetArraySize(existing_tags));
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, existing_tags) {
|
||||
cJSON_AddItemToArray(new_tags, cJSON_Duplicate(tag, 1));
|
||||
}
|
||||
printf(" New tags after adding existing: %d\n", cJSON_GetArraySize(new_tags));
|
||||
}
|
||||
|
||||
// Replace event tags with new tags
|
||||
cJSON_ReplaceItemInObject(event, "tags", new_tags);
|
||||
printf(" Final tag array size: %d\n", cJSON_GetArraySize(new_tags));
|
||||
|
||||
|
||||
cJSON_Delete(decrypted_content);
|
||||
} else {
|
||||
log_info("DEBUG: Content does not appear to be NIP-44 encrypted");
|
||||
printf(" Content starts with: %c\n", content ? content[0] : '?');
|
||||
printf(" Content length: %zu\n", content ? strlen(content) : 0);
|
||||
}
|
||||
|
||||
// Parse first tag to determine action type (now from decrypted content if applicable)
|
||||
log_info("DEBUG: Parsing first tag to determine action type");
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
if (tags_obj && cJSON_IsArray(tags_obj)) {
|
||||
printf(" Tags array size: %d\n", cJSON_GetArraySize(tags_obj));
|
||||
for (int i = 0; i < cJSON_GetArraySize(tags_obj); i++) {
|
||||
cJSON* tag = cJSON_GetArrayItem(tags_obj, i);
|
||||
if (tag && cJSON_IsArray(tag) && cJSON_GetArraySize(tag) > 0) {
|
||||
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||
if (tag_name && cJSON_IsString(tag_name)) {
|
||||
printf(" Tag %d: %s\n", i, cJSON_GetStringValue(tag_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf(" No tags array found\n");
|
||||
}
|
||||
|
||||
const char* action_type = get_first_tag_name(event);
|
||||
if (!action_type) {
|
||||
log_error("DEBUG: Missing or invalid first tag after processing");
|
||||
log_error("invalid: missing or invalid first tag");
|
||||
snprintf(error_message, error_size, "invalid: missing or invalid first tag");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("DEBUG: Action type determined");
|
||||
printf(" Action type: %s\n", action_type);
|
||||
|
||||
// Route to appropriate handler based on action type
|
||||
log_info("DEBUG: Routing to action-specific handler");
|
||||
if (strcmp(action_type, "auth_query") == 0) {
|
||||
log_info("DEBUG: Routing to auth_query handler");
|
||||
const char* query_type = get_tag_value(event, action_type, 1);
|
||||
if (!query_type) {
|
||||
log_error("DEBUG: Missing auth_query type parameter");
|
||||
log_error("invalid: missing auth_query type");
|
||||
snprintf(error_message, error_size, "invalid: missing auth_query type");
|
||||
return -1;
|
||||
}
|
||||
printf(" Query type: %s\n", query_type);
|
||||
return handle_auth_query_unified(event, query_type, error_message, error_size, wsi);
|
||||
}
|
||||
else if (strcmp(action_type, "config_query") == 0) {
|
||||
log_info("DEBUG: Routing to config_query handler");
|
||||
const char* query_type = get_tag_value(event, action_type, 1);
|
||||
if (!query_type) {
|
||||
log_error("DEBUG: Missing config_query type parameter");
|
||||
log_error("invalid: missing config_query type");
|
||||
snprintf(error_message, error_size, "invalid: missing config_query type");
|
||||
return -1;
|
||||
}
|
||||
printf(" Query type: %s\n", query_type);
|
||||
return handle_config_query_unified(event, query_type, error_message, error_size, wsi);
|
||||
}
|
||||
else if (strcmp(action_type, "config_set") == 0) {
|
||||
log_info("DEBUG: Routing to config_set handler");
|
||||
const char* config_key = get_tag_value(event, action_type, 1);
|
||||
const char* config_value = get_tag_value(event, action_type, 2);
|
||||
if (!config_key || !config_value) {
|
||||
log_error("DEBUG: Missing config_set parameters");
|
||||
log_error("invalid: missing config_set key or value");
|
||||
snprintf(error_message, error_size, "invalid: missing config_set key or value");
|
||||
return -1;
|
||||
}
|
||||
printf(" Key: %s, Value: %s\n", config_key, config_value);
|
||||
return handle_config_set_unified(event, config_key, config_value, error_message, error_size, wsi);
|
||||
}
|
||||
else if (strcmp(action_type, "config_update") == 0) {
|
||||
log_info("DEBUG: Routing to config_update handler");
|
||||
return handle_config_update_unified(event, error_message, error_size, wsi);
|
||||
}
|
||||
else if (strcmp(action_type, "system_command") == 0) {
|
||||
log_info("DEBUG: Routing to system_command handler");
|
||||
const char* command = get_tag_value(event, action_type, 1);
|
||||
if (!command) {
|
||||
log_error("DEBUG: Missing system_command type parameter");
|
||||
log_error("invalid: missing system_command type");
|
||||
snprintf(error_message, error_size, "invalid: missing system_command type");
|
||||
return -1;
|
||||
}
|
||||
printf(" Command: %s\n", command);
|
||||
return handle_system_command_unified(event, command, error_message, error_size, wsi);
|
||||
}
|
||||
else if (strcmp(action_type, "stats_query") == 0) {
|
||||
log_info("DEBUG: Routing to stats_query handler");
|
||||
return handle_stats_query_unified(event, error_message, error_size, wsi);
|
||||
}
|
||||
else if (strcmp(action_type, "whitelist") == 0 || strcmp(action_type, "blacklist") == 0) {
|
||||
log_info("DEBUG: Routing to auth rule modification handler");
|
||||
printf(" Rule type: %s\n", action_type);
|
||||
// Handle auth rule modifications (existing logic from process_admin_auth_event)
|
||||
return handle_auth_rule_modification_unified(event, error_message, error_size, wsi);
|
||||
}
|
||||
else {
|
||||
log_error("DEBUG: Unknown Kind 23456 action type");
|
||||
printf(" Unknown action: %s\n", action_type);
|
||||
char error_msg[256];
|
||||
snprintf(error_msg, sizeof(error_msg), "invalid: unknown Kind 23456 action type '%s'", action_type);
|
||||
log_error(error_msg);
|
||||
snprintf(error_message, error_size, "invalid: unknown Kind 23456 action type '%s'", action_type);
|
||||
return -1;
|
||||
}
|
||||
@@ -3196,9 +3053,6 @@ int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing unified auth query");
|
||||
printf(" Query type: %s\n", query_type);
|
||||
|
||||
const char* sql = NULL;
|
||||
int use_pattern_param = 0;
|
||||
char* pattern_value = NULL;
|
||||
@@ -3317,9 +3171,6 @@ int handle_config_query_unified(cJSON* event, const char* query_type, char* erro
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing unified config query");
|
||||
printf(" Query type: %s\n", query_type);
|
||||
|
||||
const char* sql = NULL;
|
||||
int use_pattern_param = 0;
|
||||
char* pattern_value = NULL;
|
||||
@@ -3444,10 +3295,6 @@ int handle_config_set_unified(cJSON* event, const char* config_key, const char*
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing unified config set command");
|
||||
printf(" Key: %s\n", config_key);
|
||||
printf(" Value: %s\n", config_value);
|
||||
|
||||
// Validate the configuration field before updating
|
||||
char validation_error[512];
|
||||
if (validate_config_field(config_key, config_value, validation_error, sizeof(validation_error)) != 0) {
|
||||
@@ -3530,9 +3377,6 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing unified system command");
|
||||
printf(" Command: %s\n", command);
|
||||
|
||||
if (strcmp(command, "clear_all_auth_rules") == 0) {
|
||||
// Count existing rules first
|
||||
const char* count_sql = "SELECT COUNT(*) FROM auth_rules";
|
||||
@@ -3673,19 +3517,19 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(response, "command", "system_status");
|
||||
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
||||
|
||||
|
||||
cJSON* status_data = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(status_data, "database", g_db ? "connected" : "not_available");
|
||||
cJSON_AddStringToObject(status_data, "cache_status", g_unified_cache.cache_valid ? "valid" : "invalid");
|
||||
|
||||
|
||||
if (strlen(g_database_path) > 0) {
|
||||
cJSON_AddStringToObject(status_data, "database_path", g_database_path);
|
||||
}
|
||||
|
||||
|
||||
// Count configuration items and auth rules
|
||||
if (g_db) {
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
|
||||
// Config count
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM config", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
@@ -3693,7 +3537,7 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
|
||||
// Auth rules count
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM auth_rules", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
@@ -3702,34 +3546,72 @@ int handle_system_command_unified(cJSON* event, const char* command, char* error
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cJSON_AddItemToObject(response, "data", status_data);
|
||||
|
||||
|
||||
printf("=== System Status ===\n");
|
||||
printf("Database: %s\n", g_db ? "Connected" : "Not available");
|
||||
printf("Cache status: %s\n", g_unified_cache.cache_valid ? "Valid" : "Invalid");
|
||||
|
||||
|
||||
// Get admin pubkey from event for response
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
||||
|
||||
|
||||
if (!admin_pubkey) {
|
||||
cJSON_Delete(response);
|
||||
snprintf(error_message, error_size, "missing admin pubkey for response");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Send response as signed kind 23457 event
|
||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||
log_success("System status query completed successfully with signed response");
|
||||
cJSON_Delete(response);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
cJSON_Delete(response);
|
||||
snprintf(error_message, error_size, "failed to send system status response");
|
||||
return -1;
|
||||
}
|
||||
else if (strcmp(command, "restart") == 0) {
|
||||
// Build restart acknowledgment response
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(response, "command", "restart");
|
||||
cJSON_AddStringToObject(response, "status", "initiating_restart");
|
||||
cJSON_AddStringToObject(response, "message", "Relay restart initiated - shutting down gracefully");
|
||||
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
|
||||
|
||||
printf("=== Relay Restart Initiated ===\n");
|
||||
printf("Admin requested system restart\n");
|
||||
printf("Sending acknowledgment and initiating shutdown...\n");
|
||||
|
||||
// Get admin pubkey from event for response
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
const char* admin_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
|
||||
|
||||
if (!admin_pubkey) {
|
||||
cJSON_Delete(response);
|
||||
snprintf(error_message, error_size, "missing admin pubkey for response");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Send acknowledgment response as signed kind 23457 event
|
||||
if (send_admin_response_event(response, admin_pubkey, wsi) == 0) {
|
||||
log_success("Restart acknowledgment sent successfully - initiating shutdown");
|
||||
|
||||
// Trigger graceful shutdown by setting the global shutdown flag
|
||||
g_shutdown_flag = 1;
|
||||
log_info("Shutdown flag set - relay will restart gracefully");
|
||||
|
||||
cJSON_Delete(response);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON_Delete(response);
|
||||
snprintf(error_message, error_size, "failed to send restart acknowledgment");
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
snprintf(error_message, error_size, "invalid: unknown system command '%s'", command);
|
||||
return -1;
|
||||
@@ -3858,9 +3740,6 @@ int handle_stats_query_unified(cJSON* event, char* error_message, size_t error_s
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing unified stats query");
|
||||
printf(" Query type: stats_query\n");
|
||||
|
||||
// Build response with database statistics
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(response, "query_type", "stats_query");
|
||||
@@ -4095,11 +3974,6 @@ int handle_config_update_unified(cJSON* event, char* error_message, size_t error
|
||||
const char* category = category_obj && cJSON_IsString(category_obj) ?
|
||||
cJSON_GetStringValue(category_obj) : "general";
|
||||
|
||||
log_info("Processing config object");
|
||||
printf(" Key: %s\n", key);
|
||||
printf(" Value: %s\n", value);
|
||||
printf(" Data type: %s\n", data_type);
|
||||
printf(" Category: %s\n", category);
|
||||
|
||||
// Validate the configuration field before updating
|
||||
char validation_error[512];
|
||||
@@ -4657,7 +4531,6 @@ int process_startup_config_event(const cJSON* event) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("Processing startup configuration event through admin API...");
|
||||
|
||||
// Validate event structure first
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
|
||||
1494
src/dm_admin.c
1494
src/dm_admin.c
File diff suppressed because it is too large
Load Diff
@@ -18,4 +18,10 @@ int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t err
|
||||
int is_nip17_gift_wrap_for_relay(cJSON* gift_wrap_event);
|
||||
char* generate_stats_json(void);
|
||||
|
||||
// Unified NIP-17 response functions
|
||||
int send_nip17_response(const char* sender_pubkey, const char* response_content,
|
||||
char* error_message, size_t error_size);
|
||||
char* generate_config_text(void);
|
||||
char* generate_stats_text(void);
|
||||
|
||||
#endif // DM_ADMIN_H
|
||||
File diff suppressed because one or more lines are too long
142
src/main.c
142
src/main.c
@@ -19,6 +19,7 @@
|
||||
#include "../nostr_core_lib/cjson/cJSON.h"
|
||||
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
||||
#include "../nostr_core_lib/nostr_core/nip013.h" // NIP-13: Proof of Work
|
||||
#include "../nostr_core_lib/nostr_core/nip019.h" // NIP-19: bech32-encoded entities
|
||||
#include "config.h" // Configuration management system
|
||||
#include "sql_schema.h" // Embedded database schema
|
||||
#include "websockets.h" // WebSocket protocol implementation
|
||||
@@ -45,6 +46,8 @@ int nostr_nip42_verify_auth_event(cJSON *event, const char *challenge_id,
|
||||
// Global state
|
||||
sqlite3* g_db = NULL; // Non-static so config.c can access it
|
||||
int g_server_running = 1; // Non-static so websockets.c can access it
|
||||
volatile sig_atomic_t g_shutdown_flag = 0; // Non-static so config.c can access it for restart functionality
|
||||
int g_restart_requested = 0; // Non-static so config.c can access it for restart functionality
|
||||
struct lws_context *ws_context = NULL; // Non-static so websockets.c can access it
|
||||
|
||||
// NIP-11 relay information structure
|
||||
@@ -297,59 +300,48 @@ int init_database(const char* database_path_override) {
|
||||
sqlite3_finalize(check_stmt);
|
||||
|
||||
if (has_events_table) {
|
||||
log_info("Database schema already exists, checking version");
|
||||
|
||||
// Check existing schema version and migrate if needed
|
||||
const char* version_sql = "SELECT value FROM schema_info WHERE key = 'version'";
|
||||
sqlite3_stmt* version_stmt;
|
||||
const char* db_version = NULL;
|
||||
int needs_migration = 0;
|
||||
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, version_sql, -1, &version_stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(version_stmt) == SQLITE_ROW) {
|
||||
db_version = (char*)sqlite3_column_text(version_stmt, 0);
|
||||
char version_msg[256];
|
||||
snprintf(version_msg, sizeof(version_msg), "Existing database schema version: %s",
|
||||
db_version ? db_version : "unknown");
|
||||
log_info(version_msg);
|
||||
|
||||
|
||||
// Check if migration is needed
|
||||
if (!db_version || strcmp(db_version, "5") == 0) {
|
||||
needs_migration = 1;
|
||||
log_info("Database migration needed: v5 -> v6 (adding auth_rules table)");
|
||||
} else if (strcmp(db_version, "6") == 0) {
|
||||
log_info("Database is already at current schema version v6");
|
||||
// Database is already at current schema version v6
|
||||
} else if (strcmp(db_version, EMBEDDED_SCHEMA_VERSION) == 0) {
|
||||
log_info("Database is at current schema version");
|
||||
// Database is at current schema version
|
||||
} else {
|
||||
char warning_msg[256];
|
||||
snprintf(warning_msg, sizeof(warning_msg), "Unknown database schema version: %s", db_version);
|
||||
log_warning(warning_msg);
|
||||
}
|
||||
} else {
|
||||
log_info("Database exists but no version information found, assuming migration needed");
|
||||
needs_migration = 1;
|
||||
}
|
||||
sqlite3_finalize(version_stmt);
|
||||
} else {
|
||||
log_info("Cannot read schema version, assuming migration needed");
|
||||
needs_migration = 1;
|
||||
}
|
||||
|
||||
// Perform migration if needed
|
||||
if (needs_migration) {
|
||||
log_info("Performing database schema migration to v6");
|
||||
|
||||
// Check if auth_rules table already exists
|
||||
const char* check_auth_rules_sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='auth_rules'";
|
||||
sqlite3_stmt* check_stmt;
|
||||
int has_auth_rules = 0;
|
||||
|
||||
|
||||
if (sqlite3_prepare_v2(g_db, check_auth_rules_sql, -1, &check_stmt, NULL) == SQLITE_OK) {
|
||||
has_auth_rules = (sqlite3_step(check_stmt) == SQLITE_ROW);
|
||||
sqlite3_finalize(check_stmt);
|
||||
}
|
||||
|
||||
|
||||
if (!has_auth_rules) {
|
||||
// Add auth_rules table matching sql_schema.h
|
||||
const char* create_auth_rules_sql =
|
||||
@@ -395,7 +387,7 @@ int init_database(const char* database_path_override) {
|
||||
}
|
||||
log_success("Created auth_rules indexes");
|
||||
} else {
|
||||
log_info("auth_rules table already exists, skipping creation");
|
||||
// auth_rules table already exists, skipping creation
|
||||
}
|
||||
|
||||
// Update schema version to v6
|
||||
@@ -418,8 +410,7 @@ int init_database(const char* database_path_override) {
|
||||
}
|
||||
} else {
|
||||
// Initialize database schema using embedded SQL
|
||||
log_info("Initializing database schema from embedded SQL");
|
||||
|
||||
|
||||
// Execute the embedded schema SQL
|
||||
char* error_msg = NULL;
|
||||
rc = sqlite3_exec(g_db, EMBEDDED_SCHEMA_SQL, NULL, NULL, &error_msg);
|
||||
@@ -435,12 +426,6 @@ int init_database(const char* database_path_override) {
|
||||
}
|
||||
|
||||
log_success("Database schema initialized successfully");
|
||||
|
||||
// Log schema version information
|
||||
char version_msg[256];
|
||||
snprintf(version_msg, sizeof(version_msg), "Database schema version: %s",
|
||||
EMBEDDED_SCHEMA_VERSION);
|
||||
log_info(version_msg);
|
||||
}
|
||||
} else {
|
||||
log_error("Failed to check existing database schema");
|
||||
@@ -749,8 +734,6 @@ cJSON* retrieve_event(const char* event_id) {
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, struct per_session_data *pss) {
|
||||
log_info("Handling REQ message for persistent subscription");
|
||||
|
||||
if (!cJSON_IsArray(filters)) {
|
||||
log_error("REQ filters is not an array");
|
||||
return 0;
|
||||
@@ -791,11 +774,6 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
|
||||
cJSON_Delete(filters_array);
|
||||
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg),
|
||||
"Generated %d synthetic config events for subscription %s",
|
||||
config_events_sent, sub_id);
|
||||
log_info(debug_msg);
|
||||
break; // Only generate once per subscription
|
||||
}
|
||||
}
|
||||
@@ -1072,12 +1050,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
// Default limit to prevent excessive queries
|
||||
snprintf(sql_ptr, remaining, " LIMIT 500");
|
||||
}
|
||||
|
||||
// Debug: Log the SQL query being executed
|
||||
char debug_msg[1280];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Executing SQL: %s", sql);
|
||||
log_info(debug_msg);
|
||||
|
||||
|
||||
// Execute query and send events
|
||||
sqlite3_stmt* stmt;
|
||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||
@@ -1122,11 +1095,6 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
time_t current_time = time(NULL);
|
||||
if (is_event_expired(event, current_time)) {
|
||||
// Skip this expired event
|
||||
cJSON* event_id_obj = cJSON_GetObjectItem(event, "id");
|
||||
const char* event_id = event_id_obj ? cJSON_GetStringValue(event_id_obj) : "unknown";
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Filtering expired event from subscription: %.16s", event_id);
|
||||
log_info(debug_msg);
|
||||
cJSON_Delete(event);
|
||||
continue;
|
||||
}
|
||||
@@ -1153,18 +1121,10 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
||||
cJSON_Delete(event_msg);
|
||||
events_sent++;
|
||||
}
|
||||
|
||||
char row_debug[128];
|
||||
snprintf(row_debug, sizeof(row_debug), "Query returned %d rows", row_count);
|
||||
log_info(row_debug);
|
||||
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
char events_debug[128];
|
||||
snprintf(events_debug, sizeof(events_debug), "Total events sent: %d", events_sent);
|
||||
log_info(events_debug);
|
||||
|
||||
return events_sent;
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -1227,7 +1187,6 @@ int is_authorized_admin_event(cJSON* event, char* error_buffer, size_t error_buf
|
||||
|
||||
if (!targets_this_relay) {
|
||||
// Admin event for different relay - not an error, just not for us
|
||||
log_info("Admin event targets different relay - treating as regular event");
|
||||
snprintf(error_buffer, error_buffer_size, "Admin event not targeting this relay");
|
||||
return -1;
|
||||
}
|
||||
@@ -1291,8 +1250,8 @@ void print_usage(const char* program_name) {
|
||||
printf(" -v, --version Show version information\n");
|
||||
printf(" -p, --port PORT Override relay port (first-time startup only)\n");
|
||||
printf(" --strict-port Fail if exact port is unavailable (no port increment)\n");
|
||||
printf(" -a, --admin-pubkey HEX Override admin public key (64-char hex)\n");
|
||||
printf(" -r, --relay-privkey HEX Override relay private key (64-char hex)\n");
|
||||
printf(" -a, --admin-pubkey KEY Override admin public key (64-char hex or npub)\n");
|
||||
printf(" -r, --relay-privkey KEY Override relay private key (64-char hex or nsec)\n");
|
||||
printf("\n");
|
||||
printf("Configuration:\n");
|
||||
printf(" This relay uses event-based configuration stored in the database.\n");
|
||||
@@ -1373,24 +1332,25 @@ int main(int argc, char* argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Validate public key format (must be 64 hex characters)
|
||||
if (strlen(argv[i + 1]) != 64) {
|
||||
log_error("Invalid admin public key length. Must be exactly 64 hex characters.");
|
||||
const char* input_key = argv[i + 1];
|
||||
char decoded_key[65] = {0}; // Buffer for decoded hex key
|
||||
|
||||
// Try to decode the input as either hex or npub format
|
||||
unsigned char pubkey_bytes[32];
|
||||
if (nostr_decode_npub(input_key, pubkey_bytes) == NOSTR_SUCCESS) {
|
||||
// Convert bytes back to hex string
|
||||
char* hex_ptr = decoded_key;
|
||||
for (int j = 0; j < 32; j++) {
|
||||
sprintf(hex_ptr, "%02x", pubkey_bytes[j]);
|
||||
hex_ptr += 2;
|
||||
}
|
||||
} else {
|
||||
log_error("Invalid admin public key format. Must be 64 hex characters or valid npub format.");
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Validate hex format
|
||||
for (int j = 0; j < 64; j++) {
|
||||
char c = argv[i + 1][j];
|
||||
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
|
||||
log_error("Invalid admin public key format. Must contain only hex characters (0-9, a-f, A-F).");
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(cli_options.admin_pubkey_override, argv[i + 1], sizeof(cli_options.admin_pubkey_override) - 1);
|
||||
strncpy(cli_options.admin_pubkey_override, decoded_key, sizeof(cli_options.admin_pubkey_override) - 1);
|
||||
cli_options.admin_pubkey_override[sizeof(cli_options.admin_pubkey_override) - 1] = '\0';
|
||||
i++; // Skip the key argument
|
||||
|
||||
@@ -1402,28 +1362,29 @@ int main(int argc, char* argv[]) {
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Validate private key format (must be 64 hex characters)
|
||||
if (strlen(argv[i + 1]) != 64) {
|
||||
log_error("Invalid relay private key length. Must be exactly 64 hex characters.");
|
||||
|
||||
const char* input_key = argv[i + 1];
|
||||
char decoded_key[65] = {0}; // Buffer for decoded hex key
|
||||
|
||||
// Try to decode the input as either hex or nsec format
|
||||
unsigned char privkey_bytes[32];
|
||||
if (nostr_decode_nsec(input_key, privkey_bytes) == NOSTR_SUCCESS) {
|
||||
// Convert bytes back to hex string
|
||||
char* hex_ptr = decoded_key;
|
||||
for (int j = 0; j < 32; j++) {
|
||||
sprintf(hex_ptr, "%02x", privkey_bytes[j]);
|
||||
hex_ptr += 2;
|
||||
}
|
||||
} else {
|
||||
log_error("Invalid relay private key format. Must be 64 hex characters or valid nsec format.");
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Validate hex format
|
||||
for (int j = 0; j < 64; j++) {
|
||||
char c = argv[i + 1][j];
|
||||
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
|
||||
log_error("Invalid relay private key format. Must contain only hex characters (0-9, a-f, A-F).");
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(cli_options.relay_privkey_override, argv[i + 1], sizeof(cli_options.relay_privkey_override) - 1);
|
||||
|
||||
strncpy(cli_options.relay_privkey_override, decoded_key, sizeof(cli_options.relay_privkey_override) - 1);
|
||||
cli_options.relay_privkey_override[sizeof(cli_options.relay_privkey_override) - 1] = '\0';
|
||||
i++; // Skip the key argument
|
||||
|
||||
|
||||
log_info("Relay private key override specified");
|
||||
} else if (strcmp(argv[i], "--strict-port") == 0) {
|
||||
// Strict port mode option
|
||||
@@ -1495,7 +1456,6 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
// Handle configuration setup after database is initialized
|
||||
// Always populate defaults directly in config table (abandoning legacy event signing)
|
||||
log_info("Populating config table with defaults after database initialization");
|
||||
|
||||
// Populate default config values in table
|
||||
if (populate_default_config_values() != 0) {
|
||||
@@ -1544,8 +1504,6 @@ int main(int argc, char* argv[]) {
|
||||
log_success("Relay pubkey stored in config table for first-time startup");
|
||||
}
|
||||
} else {
|
||||
log_info("Existing relay detected");
|
||||
|
||||
// Find existing database file
|
||||
char** existing_files = find_existing_db_files();
|
||||
if (!existing_files || !existing_files[0]) {
|
||||
@@ -1620,12 +1578,10 @@ int main(int argc, char* argv[]) {
|
||||
// Store both admin and relay pubkeys in config table for unified cache
|
||||
if (admin_pubkey && strlen(admin_pubkey) == 64) {
|
||||
set_config_value_in_table("admin_pubkey", admin_pubkey, "string", "Administrator public key", "authentication", 0);
|
||||
log_info("Admin pubkey stored in config table for existing relay");
|
||||
}
|
||||
|
||||
|
||||
if (relay_pubkey && strlen(relay_pubkey) == 64) {
|
||||
set_config_value_in_table("relay_pubkey", relay_pubkey, "string", "Relay public key", "relay", 0);
|
||||
log_info("Relay pubkey stored in config table for existing relay");
|
||||
}
|
||||
}
|
||||
cJSON_Delete(config_event);
|
||||
|
||||
17
src/nip009.c
17
src/nip009.c
@@ -142,11 +142,7 @@ int handle_deletion_request(cJSON* event, char* error_message, size_t error_size
|
||||
if (store_event(event) != 0) {
|
||||
log_warning("Failed to store deletion request event");
|
||||
}
|
||||
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Deletion request processed: %d events deleted", deleted_count);
|
||||
log_info(debug_msg);
|
||||
|
||||
|
||||
error_message[0] = '\0'; // Success - empty error message
|
||||
return 0;
|
||||
}
|
||||
@@ -196,10 +192,6 @@ int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids) {
|
||||
|
||||
if (sqlite3_step(delete_stmt) == SQLITE_DONE && sqlite3_changes(g_db) > 0) {
|
||||
deleted_count++;
|
||||
|
||||
char debug_msg[128];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Deleted event by ID: %.16s...", id);
|
||||
log_info(debug_msg);
|
||||
}
|
||||
sqlite3_finalize(delete_stmt);
|
||||
}
|
||||
@@ -211,9 +203,6 @@ int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids) {
|
||||
}
|
||||
} else {
|
||||
sqlite3_finalize(check_stmt);
|
||||
char debug_msg[128];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Event not found for deletion: %.16s...", id);
|
||||
log_info(debug_msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,10 +276,6 @@ int delete_events_by_address(const char* requester_pubkey, cJSON* addresses, lon
|
||||
int changes = sqlite3_changes(g_db);
|
||||
if (changes > 0) {
|
||||
deleted_count += changes;
|
||||
|
||||
char debug_msg[128];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Deleted %d events by address: %.32s...", changes, addr);
|
||||
log_info(debug_msg);
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(delete_stmt);
|
||||
|
||||
91
src/nip011.c
91
src/nip011.c
@@ -36,191 +36,128 @@ extern unified_config_cache_t g_unified_cache;
|
||||
|
||||
// Helper function to parse comma-separated string into cJSON array
|
||||
cJSON* parse_comma_separated_array(const char* csv_string) {
|
||||
log_info("parse_comma_separated_array called");
|
||||
if (!csv_string || strlen(csv_string) == 0) {
|
||||
log_info("Empty or null csv_string, returning empty array");
|
||||
return cJSON_CreateArray();
|
||||
}
|
||||
|
||||
log_info("Creating cJSON array");
|
||||
cJSON* array = cJSON_CreateArray();
|
||||
if (!array) {
|
||||
log_info("Failed to create cJSON array");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
log_info("Duplicating csv_string");
|
||||
char* csv_copy = strdup(csv_string);
|
||||
if (!csv_copy) {
|
||||
log_info("Failed to duplicate csv_string");
|
||||
cJSON_Delete(array);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
log_info("Starting token parsing");
|
||||
char* token = strtok(csv_copy, ",");
|
||||
while (token) {
|
||||
log_info("Processing token");
|
||||
// Trim whitespace
|
||||
while (*token == ' ') token++;
|
||||
char* end = token + strlen(token) - 1;
|
||||
while (end > token && *end == ' ') *end-- = '\0';
|
||||
|
||||
if (strlen(token) > 0) {
|
||||
log_info("Token has content, parsing");
|
||||
// Try to parse as number first (for supported_nips)
|
||||
char* endptr;
|
||||
long num = strtol(token, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
log_info("Token is number, adding to array");
|
||||
// It's a number
|
||||
cJSON_AddItemToArray(array, cJSON_CreateNumber(num));
|
||||
} else {
|
||||
log_info("Token is string, adding to array");
|
||||
// It's a string
|
||||
cJSON_AddItemToArray(array, cJSON_CreateString(token));
|
||||
}
|
||||
} else {
|
||||
log_info("Token is empty, skipping");
|
||||
}
|
||||
token = strtok(NULL, ",");
|
||||
}
|
||||
|
||||
log_info("Freeing csv_copy");
|
||||
free(csv_copy);
|
||||
log_info("Returning parsed array");
|
||||
return array;
|
||||
}
|
||||
|
||||
// Initialize relay information using configuration system
|
||||
void init_relay_info() {
|
||||
log_info("Initializing relay information from configuration...");
|
||||
|
||||
// Get all config values first (without holding mutex to avoid deadlock)
|
||||
// Note: These may be dynamically allocated strings that need to be freed
|
||||
log_info("Fetching relay configuration values...");
|
||||
const char* relay_name = get_config_value("relay_name");
|
||||
log_info("relay_name fetched");
|
||||
const char* relay_description = get_config_value("relay_description");
|
||||
log_info("relay_description fetched");
|
||||
const char* relay_software = get_config_value("relay_software");
|
||||
log_info("relay_software fetched");
|
||||
const char* relay_version = get_config_value("relay_version");
|
||||
log_info("relay_version fetched");
|
||||
const char* relay_contact = get_config_value("relay_contact");
|
||||
log_info("relay_contact fetched");
|
||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||
log_info("relay_pubkey fetched");
|
||||
const char* supported_nips_csv = get_config_value("supported_nips");
|
||||
log_info("supported_nips fetched");
|
||||
const char* language_tags_csv = get_config_value("language_tags");
|
||||
log_info("language_tags fetched");
|
||||
const char* relay_countries_csv = get_config_value("relay_countries");
|
||||
log_info("relay_countries fetched");
|
||||
const char* posting_policy = get_config_value("posting_policy");
|
||||
log_info("posting_policy fetched");
|
||||
const char* payments_url = get_config_value("payments_url");
|
||||
log_info("payments_url fetched");
|
||||
|
||||
// Get config values for limitations
|
||||
log_info("Fetching limitation configuration values...");
|
||||
int max_message_length = get_config_int("max_message_length", 16384);
|
||||
log_info("max_message_length fetched");
|
||||
int max_subscriptions_per_client = get_config_int("max_subscriptions_per_client", 20);
|
||||
log_info("max_subscriptions_per_client fetched");
|
||||
int max_limit = get_config_int("max_limit", 5000);
|
||||
log_info("max_limit fetched");
|
||||
int max_event_tags = get_config_int("max_event_tags", 100);
|
||||
log_info("max_event_tags fetched");
|
||||
int max_content_length = get_config_int("max_content_length", 8196);
|
||||
log_info("max_content_length fetched");
|
||||
int default_limit = get_config_int("default_limit", 500);
|
||||
log_info("default_limit fetched");
|
||||
int admin_enabled = get_config_bool("admin_enabled", 0);
|
||||
log_info("admin_enabled fetched");
|
||||
|
||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||
|
||||
// Update relay information fields
|
||||
log_info("Storing string values in cache...");
|
||||
if (relay_name) {
|
||||
log_info("Storing relay_name");
|
||||
strncpy(g_unified_cache.relay_info.name, relay_name, sizeof(g_unified_cache.relay_info.name) - 1);
|
||||
free((char*)relay_name); // Free dynamically allocated string
|
||||
log_info("relay_name stored and freed");
|
||||
} else {
|
||||
log_info("Using default relay_name");
|
||||
strncpy(g_unified_cache.relay_info.name, "C Nostr Relay", sizeof(g_unified_cache.relay_info.name) - 1);
|
||||
}
|
||||
|
||||
if (relay_description) {
|
||||
log_info("Storing relay_description");
|
||||
strncpy(g_unified_cache.relay_info.description, relay_description, sizeof(g_unified_cache.relay_info.description) - 1);
|
||||
free((char*)relay_description); // Free dynamically allocated string
|
||||
log_info("relay_description stored and freed");
|
||||
} else {
|
||||
log_info("Using default relay_description");
|
||||
strncpy(g_unified_cache.relay_info.description, "A high-performance Nostr relay implemented in C with SQLite storage", sizeof(g_unified_cache.relay_info.description) - 1);
|
||||
}
|
||||
|
||||
if (relay_software) {
|
||||
log_info("Storing relay_software");
|
||||
strncpy(g_unified_cache.relay_info.software, relay_software, sizeof(g_unified_cache.relay_info.software) - 1);
|
||||
free((char*)relay_software); // Free dynamically allocated string
|
||||
log_info("relay_software stored and freed");
|
||||
} else {
|
||||
log_info("Using default relay_software");
|
||||
strncpy(g_unified_cache.relay_info.software, "https://git.laantungir.net/laantungir/c-relay.git", sizeof(g_unified_cache.relay_info.software) - 1);
|
||||
}
|
||||
|
||||
if (relay_version) {
|
||||
log_info("Storing relay_version");
|
||||
strncpy(g_unified_cache.relay_info.version, relay_version, sizeof(g_unified_cache.relay_info.version) - 1);
|
||||
free((char*)relay_version); // Free dynamically allocated string
|
||||
log_info("relay_version stored and freed");
|
||||
} else {
|
||||
log_info("Using default relay_version");
|
||||
strncpy(g_unified_cache.relay_info.version, "0.2.0", sizeof(g_unified_cache.relay_info.version) - 1);
|
||||
}
|
||||
|
||||
if (relay_contact) {
|
||||
log_info("Storing relay_contact");
|
||||
strncpy(g_unified_cache.relay_info.contact, relay_contact, sizeof(g_unified_cache.relay_info.contact) - 1);
|
||||
free((char*)relay_contact); // Free dynamically allocated string
|
||||
log_info("relay_contact stored and freed");
|
||||
}
|
||||
|
||||
if (relay_pubkey) {
|
||||
log_info("Storing relay_pubkey");
|
||||
strncpy(g_unified_cache.relay_info.pubkey, relay_pubkey, sizeof(g_unified_cache.relay_info.pubkey) - 1);
|
||||
free((char*)relay_pubkey); // Free dynamically allocated string
|
||||
log_info("relay_pubkey stored and freed");
|
||||
}
|
||||
|
||||
if (posting_policy) {
|
||||
log_info("Storing posting_policy");
|
||||
strncpy(g_unified_cache.relay_info.posting_policy, posting_policy, sizeof(g_unified_cache.relay_info.posting_policy) - 1);
|
||||
free((char*)posting_policy); // Free dynamically allocated string
|
||||
log_info("posting_policy stored and freed");
|
||||
}
|
||||
|
||||
if (payments_url) {
|
||||
log_info("Storing payments_url");
|
||||
strncpy(g_unified_cache.relay_info.payments_url, payments_url, sizeof(g_unified_cache.relay_info.payments_url) - 1);
|
||||
free((char*)payments_url); // Free dynamically allocated string
|
||||
log_info("payments_url stored and freed");
|
||||
}
|
||||
|
||||
// Initialize supported NIPs array from config
|
||||
log_info("Initializing supported_nips array");
|
||||
if (supported_nips_csv) {
|
||||
log_info("Parsing supported_nips from config");
|
||||
g_unified_cache.relay_info.supported_nips = parse_comma_separated_array(supported_nips_csv);
|
||||
log_info("supported_nips parsed successfully");
|
||||
free((char*)supported_nips_csv); // Free dynamically allocated string
|
||||
log_info("supported_nips_csv freed");
|
||||
} else {
|
||||
log_info("Using default supported_nips");
|
||||
// Fallback to default supported NIPs
|
||||
g_unified_cache.relay_info.supported_nips = cJSON_CreateArray();
|
||||
if (g_unified_cache.relay_info.supported_nips) {
|
||||
@@ -233,14 +170,11 @@ void init_relay_info() {
|
||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(40)); // NIP-40: Expiration Timestamp
|
||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(42)); // NIP-42: Authentication
|
||||
}
|
||||
log_info("Default supported_nips created");
|
||||
}
|
||||
|
||||
// Initialize server limitations using configuration
|
||||
log_info("Initializing server limitations");
|
||||
g_unified_cache.relay_info.limitation = cJSON_CreateObject();
|
||||
if (g_unified_cache.relay_info.limitation) {
|
||||
log_info("Adding limitation fields");
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_message_length", max_message_length);
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_subscriptions", max_subscriptions_per_client);
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_limit", max_limit);
|
||||
@@ -254,25 +188,16 @@ void init_relay_info() {
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_lower_limit", 0);
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_upper_limit", 2147483647);
|
||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "default_limit", default_limit);
|
||||
log_info("Limitation fields added");
|
||||
} else {
|
||||
log_info("Failed to create limitation object");
|
||||
}
|
||||
|
||||
// Initialize empty retention policies (can be configured later)
|
||||
log_info("Initializing retention policies");
|
||||
g_unified_cache.relay_info.retention = cJSON_CreateArray();
|
||||
|
||||
// Initialize language tags from config
|
||||
log_info("Initializing language_tags");
|
||||
if (language_tags_csv) {
|
||||
log_info("Parsing language_tags from config");
|
||||
g_unified_cache.relay_info.language_tags = parse_comma_separated_array(language_tags_csv);
|
||||
log_info("language_tags parsed successfully");
|
||||
free((char*)language_tags_csv); // Free dynamically allocated string
|
||||
log_info("language_tags_csv freed");
|
||||
} else {
|
||||
log_info("Using default language_tags");
|
||||
// Fallback to global
|
||||
g_unified_cache.relay_info.language_tags = cJSON_CreateArray();
|
||||
if (g_unified_cache.relay_info.language_tags) {
|
||||
@@ -281,15 +206,10 @@ void init_relay_info() {
|
||||
}
|
||||
|
||||
// Initialize relay countries from config
|
||||
log_info("Initializing relay_countries");
|
||||
if (relay_countries_csv) {
|
||||
log_info("Parsing relay_countries from config");
|
||||
g_unified_cache.relay_info.relay_countries = parse_comma_separated_array(relay_countries_csv);
|
||||
log_info("relay_countries parsed successfully");
|
||||
free((char*)relay_countries_csv); // Free dynamically allocated string
|
||||
log_info("relay_countries_csv freed");
|
||||
} else {
|
||||
log_info("Using default relay_countries");
|
||||
// Fallback to global
|
||||
g_unified_cache.relay_info.relay_countries = cJSON_CreateArray();
|
||||
if (g_unified_cache.relay_info.relay_countries) {
|
||||
@@ -298,14 +218,11 @@ void init_relay_info() {
|
||||
}
|
||||
|
||||
// Initialize content tags as empty array
|
||||
log_info("Initializing tags");
|
||||
g_unified_cache.relay_info.tags = cJSON_CreateArray();
|
||||
|
||||
// Initialize fees as empty object (no payment required by default)
|
||||
log_info("Initializing fees");
|
||||
g_unified_cache.relay_info.fees = cJSON_CreateObject();
|
||||
|
||||
log_info("Unlocking cache mutex");
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
log_success("Relay information initialized with default values");
|
||||
@@ -518,7 +435,6 @@ cJSON* generate_relay_info_json() {
|
||||
g_unified_cache.relay_info.tags = cJSON_CreateArray();
|
||||
g_unified_cache.relay_info.fees = cJSON_CreateObject();
|
||||
|
||||
log_info("NIP-11 relay_info rebuilt directly from config table");
|
||||
}
|
||||
|
||||
// Add basic relay information
|
||||
@@ -610,7 +526,6 @@ struct nip11_session_data {
|
||||
|
||||
// Handle NIP-11 HTTP request with proper asynchronous buffer management
|
||||
int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
|
||||
log_info("Handling NIP-11 relay information request");
|
||||
|
||||
// Check if client accepts application/nostr+json
|
||||
int accepts_nostr_json = 0;
|
||||
@@ -696,9 +611,6 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
|
||||
}
|
||||
|
||||
size_t json_len = strlen(json_string);
|
||||
log_info("Generated NIP-11 JSON");
|
||||
printf(" JSON length: %zu bytes\n", json_len);
|
||||
printf(" JSON preview: %.100s%s\n", json_string, json_len > 100 ? "..." : "");
|
||||
|
||||
// Allocate session data to manage buffer lifetime across callbacks
|
||||
struct nip11_session_data* session_data = malloc(sizeof(struct nip11_session_data));
|
||||
@@ -791,8 +703,7 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
|
||||
|
||||
// Request callback for body transmission
|
||||
lws_callback_on_writable(wsi);
|
||||
|
||||
log_success("NIP-11 headers sent, body transmission scheduled");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
29
src/nip013.c
29
src/nip013.c
@@ -27,7 +27,6 @@ struct pow_config {
|
||||
|
||||
// Initialize PoW configuration using configuration system
|
||||
void init_pow_config() {
|
||||
log_info("Initializing NIP-13 Proof of Work configuration");
|
||||
|
||||
// Get all config values first (without holding mutex to avoid deadlock)
|
||||
int pow_enabled = get_config_bool("pow_enabled", 1);
|
||||
@@ -48,36 +47,20 @@ void init_pow_config() {
|
||||
g_unified_cache.pow_config.reject_lower_targets = 1;
|
||||
g_unified_cache.pow_config.strict_format = 1;
|
||||
g_unified_cache.pow_config.anti_spam_mode = 1;
|
||||
log_info("PoW configured in strict anti-spam mode");
|
||||
} else if (strcmp(pow_mode, "full") == 0) {
|
||||
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL;
|
||||
g_unified_cache.pow_config.require_nonce_tag = 1;
|
||||
log_info("PoW configured in full validation mode");
|
||||
} else if (strcmp(pow_mode, "basic") == 0) {
|
||||
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
|
||||
log_info("PoW configured in basic validation mode");
|
||||
} else if (strcmp(pow_mode, "disabled") == 0) {
|
||||
g_unified_cache.pow_config.enabled = 0;
|
||||
log_info("PoW validation disabled via configuration");
|
||||
}
|
||||
free((char*)pow_mode); // Free dynamically allocated string
|
||||
} else {
|
||||
// Default to basic mode
|
||||
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
|
||||
log_info("PoW configured in basic validation mode (default)");
|
||||
}
|
||||
|
||||
// Log final configuration
|
||||
char config_msg[512];
|
||||
snprintf(config_msg, sizeof(config_msg),
|
||||
"PoW Configuration: enabled=%s, min_difficulty=%d, validation_flags=0x%x, mode=%s",
|
||||
g_unified_cache.pow_config.enabled ? "true" : "false",
|
||||
g_unified_cache.pow_config.min_pow_difficulty,
|
||||
g_unified_cache.pow_config.validation_flags,
|
||||
g_unified_cache.pow_config.anti_spam_mode ? "anti-spam" :
|
||||
(g_unified_cache.pow_config.validation_flags & NOSTR_POW_VALIDATE_FULL) ? "full" : "basic");
|
||||
log_info(config_msg);
|
||||
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
}
|
||||
|
||||
@@ -175,17 +158,5 @@ int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
||||
return validation_result;
|
||||
}
|
||||
|
||||
// Log successful PoW validation (only if minimum difficulty is required)
|
||||
if (min_pow_difficulty > 0 || pow_result.has_nonce_tag) {
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg),
|
||||
"PoW validated: difficulty=%d, target=%d, nonce=%llu%s",
|
||||
pow_result.actual_difficulty,
|
||||
pow_result.committed_target,
|
||||
(unsigned long long)pow_result.nonce_value,
|
||||
pow_result.has_nonce_tag ? "" : " (no nonce tag)");
|
||||
log_info(debug_msg);
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
16
src/nip040.c
16
src/nip040.c
@@ -34,7 +34,6 @@ void log_warning(const char* message);
|
||||
|
||||
// Initialize expiration configuration using configuration system
|
||||
void init_expiration_config() {
|
||||
log_info("Initializing NIP-40 Expiration Timestamp configuration");
|
||||
|
||||
// Get all config values first (without holding mutex to avoid deadlock)
|
||||
int expiration_enabled = get_config_bool("expiration_enabled", 1);
|
||||
@@ -56,15 +55,6 @@ void init_expiration_config() {
|
||||
g_expiration_config.grace_period = 300;
|
||||
}
|
||||
|
||||
// Log final configuration
|
||||
char config_msg[512];
|
||||
snprintf(config_msg, sizeof(config_msg),
|
||||
"Expiration Configuration: enabled=%s, strict_mode=%s, filter_responses=%s, grace_period=%ld seconds",
|
||||
g_expiration_config.enabled ? "true" : "false",
|
||||
g_expiration_config.strict_mode ? "true" : "false",
|
||||
g_expiration_config.filter_responses ? "true" : "false",
|
||||
g_expiration_config.grace_period);
|
||||
log_info(config_msg);
|
||||
}
|
||||
|
||||
// Extract expiration timestamp from event tags
|
||||
@@ -161,11 +151,7 @@ int validate_event_expiration(cJSON* event, char* error_message, size_t error_si
|
||||
log_warning("Event rejected: expired timestamp");
|
||||
return -1;
|
||||
} else {
|
||||
// In non-strict mode, log but allow expired events
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg),
|
||||
"Accepting expired event (strict_mode disabled)");
|
||||
log_info(debug_msg);
|
||||
// In non-strict mode, allow expired events
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,10 +83,6 @@ void send_nip42_auth_challenge(struct lws* wsi, struct per_session_data* pss) {
|
||||
free(msg_str);
|
||||
}
|
||||
cJSON_Delete(auth_msg);
|
||||
|
||||
char debug_msg[128];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "NIP-42 auth challenge sent: %.16s...", challenge);
|
||||
log_info(debug_msg);
|
||||
}
|
||||
|
||||
// Handle NIP-42 signed authentication event from client
|
||||
|
||||
@@ -139,21 +139,6 @@ struct {
|
||||
char reason[500]; // specific reason string
|
||||
} g_last_rule_violation = {0};
|
||||
|
||||
/**
|
||||
* Helper function for consistent debug logging to main relay.log file
|
||||
*/
|
||||
static void validator_debug_log(const char *message) {
|
||||
FILE *relay_log = fopen("relay.log", "a");
|
||||
if (relay_log) {
|
||||
// Use same format as main logging system
|
||||
time_t now = time(NULL);
|
||||
struct tm *tm_info = localtime(&now);
|
||||
char timestamp[20];
|
||||
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info);
|
||||
fprintf(relay_log, "[%s] [DEBUG] %s", timestamp, message);
|
||||
fclose(relay_log);
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// FORWARD DECLARATIONS
|
||||
@@ -188,16 +173,12 @@ int ginxsom_request_validator_init(const char *db_path, const char *app_name) {
|
||||
|
||||
// Initialize nostr_core_lib if not already done
|
||||
if (nostr_crypto_init() != NOSTR_SUCCESS) {
|
||||
validator_debug_log(
|
||||
"VALIDATOR: Failed to initialize nostr crypto system\n");
|
||||
return NOSTR_ERROR_CRYPTO_INIT;
|
||||
}
|
||||
|
||||
// Load initial configuration from database
|
||||
int result = reload_auth_config();
|
||||
if (result != NOSTR_SUCCESS) {
|
||||
validator_debug_log(
|
||||
"VALIDATOR: Failed to load configuration from database\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -215,8 +196,6 @@ int ginxsom_request_validator_init(const char *db_path, const char *app_name) {
|
||||
g_challenge_manager.last_cleanup = time(NULL);
|
||||
|
||||
g_validator_initialized = 1;
|
||||
validator_debug_log(
|
||||
"VALIDATOR: Request validator initialized successfully\n");
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -257,20 +236,17 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
|
||||
// 1. Null Pointer Checks - Reject malformed requests instantly
|
||||
if (!json_string || json_length == 0) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 1 FAILED - Null input\n");
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
// 2. Initialization Check - Verify system is properly initialized
|
||||
if (!g_validator_initialized) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 2 FAILED - Validator not initialized\n");
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
// 3. Parse JSON string to cJSON event object
|
||||
cJSON *event = cJSON_ParseWithLength(json_string, json_length);
|
||||
if (!event) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 3 FAILED - Failed to parse JSON event\n");
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
@@ -290,20 +266,14 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
!tags || !cJSON_IsArray(tags) ||
|
||||
!content || !cJSON_IsString(content) ||
|
||||
!sig || !cJSON_IsString(sig)) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 4 FAILED - Invalid event structure\n");
|
||||
cJSON_Delete(event);
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int event_kind = (int)cJSON_GetNumberValue(kind);
|
||||
|
||||
|
||||
// 5. Check configuration using unified cache
|
||||
int auth_required = nostr_auth_rules_enabled();
|
||||
|
||||
char config_msg[256];
|
||||
sprintf(config_msg, "VALIDATOR_DEBUG: STEP 5 PASSED - Event kind: %d, auth_required: %d\n",
|
||||
event_kind, auth_required);
|
||||
validator_debug_log(config_msg);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// PHASE 2: NOSTR EVENT VALIDATION
|
||||
@@ -312,39 +282,26 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
// 6. Nostr Event Structure Validation using nostr_core_lib
|
||||
int validation_result = nostr_validate_event(event);
|
||||
if (validation_result != NOSTR_SUCCESS) {
|
||||
char validation_msg[256];
|
||||
sprintf(validation_msg, "VALIDATOR_DEBUG: STEP 6 FAILED - NOSTR event validation failed (error=%d)\n",
|
||||
validation_result);
|
||||
validator_debug_log(validation_msg);
|
||||
cJSON_Delete(event);
|
||||
return validation_result;
|
||||
}
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 6 PASSED - Event structure and signature valid\n");
|
||||
|
||||
// 7. Extract pubkey for rule evaluation
|
||||
const char *event_pubkey = cJSON_GetStringValue(pubkey);
|
||||
if (!event_pubkey || strlen(event_pubkey) != 64) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 7 FAILED - Invalid pubkey format\n");
|
||||
cJSON_Delete(event);
|
||||
return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
|
||||
}
|
||||
|
||||
char pubkey_msg[256];
|
||||
sprintf(pubkey_msg, "VALIDATOR_DEBUG: STEP 7 PASSED - Extracted pubkey: %.16s...\n", event_pubkey);
|
||||
validator_debug_log(pubkey_msg);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// PHASE 3: EVENT KIND SPECIFIC VALIDATION
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
// 8. Handle NIP-42 authentication challenge events (kind 22242)
|
||||
if (event_kind == 22242) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 8 - Processing NIP-42 challenge response\n");
|
||||
|
||||
// Check NIP-42 mode using unified cache
|
||||
const char* nip42_enabled = get_config_value("nip42_auth_enabled");
|
||||
if (nip42_enabled && strcmp(nip42_enabled, "false") == 0) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 8 FAILED - NIP-42 is disabled\n");
|
||||
free((char*)nip42_enabled);
|
||||
cJSON_Delete(event);
|
||||
return NOSTR_ERROR_NIP42_DISABLED;
|
||||
@@ -353,7 +310,6 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
|
||||
// TODO: Implement full NIP-42 challenge validation
|
||||
// For now, accept all valid NIP-42 events
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 8 PASSED - NIP-42 challenge response accepted\n");
|
||||
cJSON_Delete(event);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
@@ -364,10 +320,8 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
|
||||
// 9. Check if authentication rules are enabled
|
||||
if (!auth_required) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 9 - Authentication disabled, skipping database auth rules\n");
|
||||
} else {
|
||||
// 10. Check database authentication rules (only if auth enabled)
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 10 - Checking database authentication rules\n");
|
||||
|
||||
// Create operation string with event kind for more specific rule matching
|
||||
char operation_str[64];
|
||||
@@ -379,17 +333,10 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
// If generic event check fails, try specific event kind check
|
||||
rules_result = check_database_auth_rules(event_pubkey, operation_str, NULL);
|
||||
if (rules_result != NOSTR_SUCCESS) {
|
||||
char rules_msg[256];
|
||||
sprintf(rules_msg, "VALIDATOR_DEBUG: STEP 10 FAILED - Database rules denied request (kind=%d)\n", event_kind);
|
||||
validator_debug_log(rules_msg);
|
||||
cJSON_Delete(event);
|
||||
return rules_result;
|
||||
}
|
||||
}
|
||||
|
||||
char rules_success_msg[256];
|
||||
sprintf(rules_success_msg, "VALIDATOR_DEBUG: STEP 10 PASSED - Database rules allow request (kind=%d)\n", event_kind);
|
||||
validator_debug_log(rules_success_msg);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
@@ -404,44 +351,30 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||
|
||||
if (pow_enabled && pow_min_difficulty > 0) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 11 - Validating NIP-13 Proof of Work\n");
|
||||
|
||||
nostr_pow_result_t pow_result;
|
||||
int pow_validation_result = nostr_validate_pow(event, pow_min_difficulty,
|
||||
pow_validation_flags, &pow_result);
|
||||
|
||||
|
||||
if (pow_validation_result != NOSTR_SUCCESS) {
|
||||
char pow_msg[256];
|
||||
sprintf(pow_msg, "VALIDATOR_DEBUG: STEP 11 FAILED - PoW validation failed (error=%d, difficulty=%d/%d)\n",
|
||||
pow_validation_result, pow_result.actual_difficulty, pow_min_difficulty);
|
||||
validator_debug_log(pow_msg);
|
||||
cJSON_Delete(event);
|
||||
return pow_validation_result;
|
||||
}
|
||||
|
||||
char pow_success_msg[256];
|
||||
sprintf(pow_success_msg, "VALIDATOR_DEBUG: STEP 11 PASSED - PoW validated (difficulty=%d, target=%d)\n",
|
||||
pow_result.actual_difficulty, pow_result.committed_target);
|
||||
validator_debug_log(pow_success_msg);
|
||||
} else {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 11 SKIPPED - PoW validation disabled or min_difficulty=0\n");
|
||||
}
|
||||
|
||||
// 12. NIP-40 Expiration validation
|
||||
// Always check expiration tags if present (following NIP-40 specification)
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 12 - Starting NIP-40 Expiration validation\n");
|
||||
|
||||
|
||||
cJSON *expiration_tag = NULL;
|
||||
cJSON *tags_array = cJSON_GetObjectItem(event, "tags");
|
||||
|
||||
|
||||
if (tags_array && cJSON_IsArray(tags_array)) {
|
||||
cJSON *tag = NULL;
|
||||
cJSON_ArrayForEach(tag, tags_array) {
|
||||
if (!cJSON_IsArray(tag)) continue;
|
||||
|
||||
|
||||
cJSON *tag_name = cJSON_GetArrayItem(tag, 0);
|
||||
if (!tag_name || !cJSON_IsString(tag_name)) continue;
|
||||
|
||||
|
||||
const char *tag_name_str = cJSON_GetStringValue(tag_name);
|
||||
if (strcmp(tag_name_str, "expiration") == 0) {
|
||||
cJSON *tag_value = cJSON_GetArrayItem(tag, 1);
|
||||
@@ -452,57 +385,40 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (expiration_tag) {
|
||||
const char *expiration_str = cJSON_GetStringValue(expiration_tag);
|
||||
|
||||
|
||||
// Validate that the expiration string contains only digits (and optional leading whitespace)
|
||||
const char* p = expiration_str;
|
||||
|
||||
|
||||
// Skip leading whitespace
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
|
||||
|
||||
// Check if we have at least one digit
|
||||
if (*p == '\0') {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 12 SKIPPED - Empty expiration tag value, ignoring\n");
|
||||
} else {
|
||||
// Validate that all remaining characters are digits
|
||||
const char* digit_start = p;
|
||||
while (*p >= '0' && *p <= '9') p++;
|
||||
|
||||
|
||||
// If we didn't consume the entire string or found no digits, it's malformed
|
||||
if (*p != '\0' || p == digit_start) {
|
||||
char malformed_msg[256];
|
||||
sprintf(malformed_msg, "VALIDATOR_DEBUG: STEP 12 SKIPPED - Malformed expiration tag value '%.32s', ignoring\n",
|
||||
expiration_str);
|
||||
validator_debug_log(malformed_msg);
|
||||
} else {
|
||||
// Valid numeric string, parse and check expiration
|
||||
time_t expiration_time = (time_t)atol(expiration_str);
|
||||
time_t now = time(NULL);
|
||||
int grace_period = get_config_int("nip40_expiration_grace_period", 60);
|
||||
|
||||
|
||||
if (expiration_time > 0 && now > expiration_time + grace_period) {
|
||||
char exp_msg[256];
|
||||
sprintf(exp_msg, "VALIDATOR_DEBUG: STEP 12 FAILED - Event expired (now=%ld, exp=%ld, grace=%d)\n",
|
||||
(long)now, (long)expiration_time, grace_period);
|
||||
validator_debug_log(exp_msg);
|
||||
cJSON_Delete(event);
|
||||
return NOSTR_ERROR_EVENT_EXPIRED;
|
||||
}
|
||||
|
||||
char exp_success_msg[256];
|
||||
sprintf(exp_success_msg, "VALIDATOR_DEBUG: STEP 12 PASSED - Event not expired (exp=%ld, now=%ld)\n",
|
||||
(long)expiration_time, (long)now);
|
||||
validator_debug_log(exp_success_msg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 12 SKIPPED - No expiration tag found\n");
|
||||
}
|
||||
|
||||
// All validations passed
|
||||
validator_debug_log("VALIDATOR_DEBUG: STEP 13 PASSED - All validations complete, event ACCEPTED\n");
|
||||
cJSON_Delete(event);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
@@ -578,7 +494,6 @@ void nostr_request_result_free_file_data(nostr_request_result_t *result) {
|
||||
void nostr_request_validator_force_cache_refresh(void) {
|
||||
// Use unified cache refresh from config.c
|
||||
force_config_cache_refresh();
|
||||
validator_debug_log("VALIDATOR: Cache forcibly invalidated via unified cache\n");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -586,7 +501,6 @@ void nostr_request_validator_force_cache_refresh(void) {
|
||||
*/
|
||||
static int reload_auth_config(void) {
|
||||
// Configuration is now handled by the unified cache in config.c
|
||||
validator_debug_log("VALIDATOR: Using unified cache system for configuration\n");
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -598,35 +512,23 @@ static int reload_auth_config(void) {
|
||||
* Check database authentication rules for the request
|
||||
* Implements the 6-step rule evaluation engine from AUTH_API.md
|
||||
*/
|
||||
int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
int check_database_auth_rules(const char *pubkey, const char *operation __attribute__((unused)),
|
||||
const char *resource_hash) {
|
||||
sqlite3 *db = NULL;
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
int rc;
|
||||
|
||||
if (!pubkey) {
|
||||
validator_debug_log(
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Missing pubkey for rule evaluation\n");
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
char rules_msg[256];
|
||||
sprintf(rules_msg,
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Checking rules for pubkey=%.32s..., "
|
||||
"operation=%s\n",
|
||||
pubkey, operation ? operation : "NULL");
|
||||
validator_debug_log(rules_msg);
|
||||
|
||||
// Open database using global database path
|
||||
if (strlen(g_database_path) == 0) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - No database path available\n");
|
||||
return NOSTR_SUCCESS; // Default allow on DB error
|
||||
}
|
||||
|
||||
|
||||
rc = sqlite3_open_v2(g_database_path, &db, SQLITE_OPEN_READONLY, NULL);
|
||||
if (rc != SQLITE_OK) {
|
||||
validator_debug_log(
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Failed to open database\n");
|
||||
return NOSTR_SUCCESS; // Default allow on DB error
|
||||
}
|
||||
|
||||
@@ -640,13 +542,6 @@ int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char *action = (const char *)sqlite3_column_text(stmt, 1);
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 1 FAILED - "
|
||||
"Pubkey blacklisted\n");
|
||||
char blacklist_msg[256];
|
||||
sprintf(blacklist_msg,
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Blacklist rule matched: action=%s\n",
|
||||
action ? action : "deny");
|
||||
validator_debug_log(blacklist_msg);
|
||||
|
||||
// Set specific violation details for status code mapping
|
||||
strcpy(g_last_rule_violation.violation_type, "pubkey_blacklist");
|
||||
@@ -659,8 +554,6 @@ int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 1 PASSED - Pubkey "
|
||||
"not blacklisted\n");
|
||||
|
||||
// Step 2: Check hash blacklist
|
||||
if (resource_hash) {
|
||||
@@ -673,14 +566,6 @@ int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char *action = (const char *)sqlite3_column_text(stmt, 1);
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 FAILED - "
|
||||
"Hash blacklisted\n");
|
||||
char hash_blacklist_msg[256];
|
||||
sprintf(
|
||||
hash_blacklist_msg,
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Hash blacklist rule matched: action=%s\n",
|
||||
action ? action : "deny");
|
||||
validator_debug_log(hash_blacklist_msg);
|
||||
|
||||
// Set specific violation details for status code mapping
|
||||
strcpy(g_last_rule_violation.violation_type, "hash_blacklist");
|
||||
@@ -693,11 +578,6 @@ int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 PASSED - Hash "
|
||||
"not blacklisted\n");
|
||||
} else {
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 2 SKIPPED - No "
|
||||
"resource hash provided\n");
|
||||
}
|
||||
|
||||
// Step 3: Check pubkey whitelist
|
||||
@@ -709,22 +589,12 @@ int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);
|
||||
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char *action = (const char *)sqlite3_column_text(stmt, 1);
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 PASSED - "
|
||||
"Pubkey whitelisted\n");
|
||||
char whitelist_msg[256];
|
||||
sprintf(whitelist_msg,
|
||||
"VALIDATOR_DEBUG: RULES ENGINE - Whitelist rule matched: action=%s\n",
|
||||
action ? action : "allow");
|
||||
validator_debug_log(whitelist_msg);
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
return NOSTR_SUCCESS; // Allow whitelisted pubkey
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 3 FAILED - Pubkey "
|
||||
"not whitelisted\n");
|
||||
|
||||
// Step 4: Check if any whitelist rules exist - if yes, deny by default
|
||||
const char *whitelist_exists_sql =
|
||||
@@ -735,9 +605,6 @@ int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
int whitelist_count = sqlite3_column_int(stmt, 0);
|
||||
if (whitelist_count > 0) {
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 4 FAILED - "
|
||||
"Whitelist exists but pubkey not in it\n");
|
||||
|
||||
// Set specific violation details for status code mapping
|
||||
strcpy(g_last_rule_violation.violation_type, "whitelist_violation");
|
||||
strcpy(g_last_rule_violation.reason,
|
||||
@@ -750,12 +617,8 @@ int check_database_auth_rules(const char *pubkey, const char *operation,
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 4 PASSED - No "
|
||||
"whitelist restrictions apply\n");
|
||||
|
||||
sqlite3_close(db);
|
||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 5 PASSED - All "
|
||||
"rule checks completed, default ALLOW\n");
|
||||
return NOSTR_SUCCESS; // Default allow if no restrictive rules matched
|
||||
}
|
||||
|
||||
@@ -821,11 +684,6 @@ static void cleanup_expired_challenges(void) {
|
||||
}
|
||||
|
||||
g_challenge_manager.last_cleanup = now;
|
||||
|
||||
char cleanup_msg[256];
|
||||
sprintf(cleanup_msg, "NIP-42: Cleaned up challenges, %d active remaining\n",
|
||||
active_count);
|
||||
validator_debug_log(cleanup_msg);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -877,12 +735,6 @@ static int store_challenge(const char *challenge_id, const char *client_ip) {
|
||||
entry->expires_at = now + g_challenge_manager.timeout_seconds;
|
||||
entry->active = 1;
|
||||
|
||||
char store_msg[256];
|
||||
sprintf(store_msg,
|
||||
"NIP-42: Stored challenge %.16s... (expires in %d seconds)\n",
|
||||
challenge_id, g_challenge_manager.timeout_seconds);
|
||||
validator_debug_log(store_msg);
|
||||
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -209,12 +209,7 @@ int add_subscription_to_manager(subscription_t* sub) {
|
||||
|
||||
// Log subscription creation to database
|
||||
log_subscription_created(sub);
|
||||
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Added subscription '%s' (total: %d)",
|
||||
sub->id, g_subscription_manager.total_subscriptions);
|
||||
log_info(debug_msg);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -242,12 +237,7 @@ int remove_subscription_from_manager(const char* sub_id, struct lws* wsi) {
|
||||
|
||||
// Update events sent counter before freeing
|
||||
update_subscription_events_sent(sub_id, sub->events_sent);
|
||||
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Removed subscription '%s' (total: %d)",
|
||||
sub_id, g_subscription_manager.total_subscriptions);
|
||||
log_info(debug_msg);
|
||||
|
||||
|
||||
free_subscription(sub);
|
||||
return 0;
|
||||
}
|
||||
@@ -274,34 +264,24 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
if (filter->kinds && cJSON_IsArray(filter->kinds)) {
|
||||
cJSON* event_kind = cJSON_GetObjectItem(event, "kind");
|
||||
if (!event_kind || !cJSON_IsNumber(event_kind)) {
|
||||
log_info("DEBUG FILTER: kinds filter present but event has no valid kind");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int event_kind_val = (int)cJSON_GetNumberValue(event_kind);
|
||||
int kind_match = 0;
|
||||
|
||||
char debug_kinds_msg[256];
|
||||
snprintf(debug_kinds_msg, sizeof(debug_kinds_msg), "DEBUG FILTER: Checking kinds - event kind: %d", event_kind_val);
|
||||
log_info(debug_kinds_msg);
|
||||
|
||||
cJSON* kind_item = NULL;
|
||||
cJSON_ArrayForEach(kind_item, filter->kinds) {
|
||||
if (cJSON_IsNumber(kind_item)) {
|
||||
int filter_kind = (int)cJSON_GetNumberValue(kind_item);
|
||||
char debug_kind_check_msg[256];
|
||||
snprintf(debug_kind_check_msg, sizeof(debug_kind_check_msg), "DEBUG FILTER: Comparing event kind %d with filter kind %d", event_kind_val, filter_kind);
|
||||
log_info(debug_kind_check_msg);
|
||||
if (filter_kind == event_kind_val) {
|
||||
kind_match = 1;
|
||||
log_info("DEBUG FILTER: Kind match found!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!kind_match) {
|
||||
log_info("DEBUG FILTER: No kind match - filter fails");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -390,14 +370,9 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
if (filter->tag_filters && cJSON_IsObject(filter->tag_filters)) {
|
||||
cJSON* event_tags = cJSON_GetObjectItem(event, "tags");
|
||||
if (!event_tags || !cJSON_IsArray(event_tags)) {
|
||||
log_info("DEBUG FILTER: tag filters present but event has no valid tags array");
|
||||
return 0; // Event has no tags but filter requires tags
|
||||
}
|
||||
|
||||
char debug_tags_msg[256];
|
||||
snprintf(debug_tags_msg, sizeof(debug_tags_msg), "DEBUG FILTER: Checking tag filters - event has %d tags", cJSON_GetArraySize(event_tags));
|
||||
log_info(debug_tags_msg);
|
||||
|
||||
// Check each tag filter
|
||||
cJSON* tag_filter = NULL;
|
||||
cJSON_ArrayForEach(tag_filter, filter->tag_filters) {
|
||||
@@ -407,10 +382,6 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
|
||||
const char* tag_name = tag_filter->string + 1; // Skip the '#'
|
||||
|
||||
char debug_tag_filter_msg[256];
|
||||
snprintf(debug_tag_filter_msg, sizeof(debug_tag_filter_msg), "DEBUG FILTER: Checking tag filter #%s", tag_name);
|
||||
log_info(debug_tag_filter_msg);
|
||||
|
||||
if (!cJSON_IsArray(tag_filter)) {
|
||||
continue; // Tag filter must be an array
|
||||
}
|
||||
@@ -434,28 +405,16 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
const char* event_tag_name_str = cJSON_GetStringValue(event_tag_name);
|
||||
const char* event_tag_value_str = cJSON_GetStringValue(event_tag_value);
|
||||
|
||||
char debug_event_tag_msg[256];
|
||||
snprintf(debug_event_tag_msg, sizeof(debug_event_tag_msg), "DEBUG FILTER: Event tag: %s = %s", event_tag_name_str, event_tag_value_str);
|
||||
log_info(debug_event_tag_msg);
|
||||
|
||||
// Check if tag name matches
|
||||
if (strcmp(event_tag_name_str, tag_name) == 0) {
|
||||
char debug_tag_name_match_msg[256];
|
||||
snprintf(debug_tag_name_match_msg, sizeof(debug_tag_name_match_msg), "DEBUG FILTER: Tag name '%s' matches filter", tag_name);
|
||||
log_info(debug_tag_name_match_msg);
|
||||
|
||||
// Check if any of the filter values match this tag value
|
||||
cJSON* filter_value = NULL;
|
||||
cJSON_ArrayForEach(filter_value, tag_filter) {
|
||||
if (cJSON_IsString(filter_value)) {
|
||||
const char* filter_value_str = cJSON_GetStringValue(filter_value);
|
||||
char debug_filter_value_msg[256];
|
||||
snprintf(debug_filter_value_msg, sizeof(debug_filter_value_msg), "DEBUG FILTER: Comparing event tag value '%s' with filter value '%s'", event_tag_value_str, filter_value_str);
|
||||
log_info(debug_filter_value_msg);
|
||||
// Support prefix matching for tag values
|
||||
if (strncmp(event_tag_value_str, filter_value_str, strlen(filter_value_str)) == 0) {
|
||||
tag_match = 1;
|
||||
log_info("DEBUG FILTER: Tag value match found!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -468,9 +427,6 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
}
|
||||
|
||||
if (!tag_match) {
|
||||
char debug_tag_fail_msg[256];
|
||||
snprintf(debug_tag_fail_msg, sizeof(debug_tag_fail_msg), "DEBUG FILTER: Tag filter #%s failed - no match found", tag_name);
|
||||
log_info(debug_tag_fail_msg);
|
||||
return 0; // This tag filter didn't match, so the event doesn't match
|
||||
}
|
||||
}
|
||||
@@ -482,40 +438,17 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
||||
// Check if an event matches any filter in a subscription (filters are OR'd together)
|
||||
int event_matches_subscription(cJSON* event, subscription_t* subscription) {
|
||||
if (!event || !subscription || !subscription->filters) {
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "DEBUG MATCH: Subscription '%s' - invalid parameters (event: %p, subscription: %p, filters: %p)",
|
||||
subscription ? subscription->id : "NULL", event, subscription, subscription ? subscription->filters : NULL);
|
||||
log_info(debug_msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char debug_start_msg[256];
|
||||
snprintf(debug_start_msg, sizeof(debug_start_msg), "DEBUG MATCH: Checking subscription '%s' filters", subscription->id);
|
||||
log_info(debug_start_msg);
|
||||
|
||||
subscription_filter_t* filter = subscription->filters;
|
||||
int filter_index = 0;
|
||||
while (filter) {
|
||||
int filter_matches = event_matches_filter(event, filter);
|
||||
char debug_filter_msg[256];
|
||||
snprintf(debug_filter_msg, sizeof(debug_filter_msg), "DEBUG MATCH: Subscription '%s' filter %d - %s",
|
||||
subscription->id, filter_index, filter_matches ? "MATCHES" : "no match");
|
||||
log_info(debug_filter_msg);
|
||||
|
||||
if (filter_matches) {
|
||||
char debug_match_msg[256];
|
||||
snprintf(debug_match_msg, sizeof(debug_match_msg), "DEBUG MATCH: Subscription '%s' - MATCH FOUND", subscription->id);
|
||||
log_info(debug_match_msg);
|
||||
if (event_matches_filter(event, filter)) {
|
||||
return 1; // Match found (OR logic)
|
||||
}
|
||||
filter = filter->next;
|
||||
filter_index++;
|
||||
}
|
||||
|
||||
char debug_no_match_msg[256];
|
||||
snprintf(debug_no_match_msg, sizeof(debug_no_match_msg), "DEBUG MATCH: Subscription '%s' - NO MATCHES", subscription->id);
|
||||
log_info(debug_no_match_msg);
|
||||
|
||||
return 0; // No filters matched
|
||||
}
|
||||
|
||||
@@ -534,11 +467,6 @@ int broadcast_event_to_subscriptions(cJSON* event) {
|
||||
if (expiration_enabled && filter_responses) {
|
||||
time_t current_time = time(NULL);
|
||||
if (is_event_expired(event, current_time)) {
|
||||
char debug_msg[256];
|
||||
cJSON* event_id_obj = cJSON_GetObjectItem(event, "id");
|
||||
const char* event_id = event_id_obj ? cJSON_GetStringValue(event_id_obj) : "unknown";
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Skipping broadcast of expired event: %.16s", event_id);
|
||||
log_info(debug_msg);
|
||||
return 0; // Don't broadcast expired events
|
||||
}
|
||||
}
|
||||
@@ -547,25 +475,8 @@ int broadcast_event_to_subscriptions(cJSON* event) {
|
||||
|
||||
pthread_mutex_lock(&g_subscription_manager.subscriptions_lock);
|
||||
|
||||
// Debug: Log total active subscriptions
|
||||
int total_active = 0;
|
||||
subscription_t* count_sub = g_subscription_manager.active_subscriptions;
|
||||
while (count_sub) {
|
||||
total_active++;
|
||||
count_sub = count_sub->next;
|
||||
}
|
||||
|
||||
char debug_total_msg[128];
|
||||
snprintf(debug_total_msg, sizeof(debug_total_msg), "DEBUG BROADCAST: Broadcasting event to %d total active subscriptions", total_active);
|
||||
log_info(debug_total_msg);
|
||||
|
||||
subscription_t* sub = g_subscription_manager.active_subscriptions;
|
||||
while (sub) {
|
||||
// Debug: Log each subscription being checked
|
||||
char debug_sub_msg[256];
|
||||
snprintf(debug_sub_msg, sizeof(debug_sub_msg), "DEBUG BROADCAST: Checking subscription '%s' (active: %d)", sub->id, sub->active);
|
||||
log_info(debug_sub_msg);
|
||||
|
||||
if (sub->active && event_matches_subscription(event, sub)) {
|
||||
// Create EVENT message for this subscription
|
||||
cJSON* event_msg = cJSON_CreateArray();
|
||||
@@ -606,15 +517,9 @@ int broadcast_event_to_subscriptions(cJSON* event) {
|
||||
|
||||
// Update global statistics
|
||||
g_subscription_manager.total_events_broadcast += broadcasts;
|
||||
|
||||
|
||||
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
|
||||
|
||||
if (broadcasts > 0) {
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Broadcasted event to %d subscriptions", broadcasts);
|
||||
log_info(debug_msg);
|
||||
}
|
||||
|
||||
|
||||
return broadcasts;
|
||||
}
|
||||
|
||||
|
||||
213
src/websockets.c
213
src/websockets.c
@@ -95,6 +95,8 @@ extern unified_config_cache_t g_unified_cache;
|
||||
// Forward declarations for global state
|
||||
extern sqlite3* g_db;
|
||||
extern int g_server_running;
|
||||
extern volatile sig_atomic_t g_shutdown_flag;
|
||||
extern int g_restart_requested;
|
||||
extern struct lws_context *ws_context;
|
||||
|
||||
// Global subscription manager
|
||||
@@ -118,7 +120,6 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Handle HTTP requests
|
||||
{
|
||||
char *requested_uri = (char *)in;
|
||||
log_info("HTTP request received");
|
||||
|
||||
// Check if this is an OPTIONS request
|
||||
char method[16] = {0};
|
||||
@@ -184,20 +185,12 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Handle HTTP body transmission for NIP-11 or embedded files
|
||||
{
|
||||
void* user_data = lws_wsi_user(wsi);
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "HTTP_WRITEABLE: user_data=%p", user_data);
|
||||
log_info(debug_msg);
|
||||
if (user_data) {
|
||||
int type = *(int*)user_data;
|
||||
if (type == 0) {
|
||||
// NIP-11
|
||||
struct nip11_session_data* session_data = (struct nip11_session_data*)user_data;
|
||||
snprintf(debug_msg, sizeof(debug_msg), "NIP-11: session_data=%p, type=%d, json_length=%zu, headers_sent=%d, body_sent=%d",
|
||||
session_data, session_data->type, session_data->json_length, session_data->headers_sent, session_data->body_sent);
|
||||
log_info(debug_msg);
|
||||
if (session_data->headers_sent && !session_data->body_sent) {
|
||||
snprintf(debug_msg, sizeof(debug_msg), "NIP-11: Attempting to send body, json_length=%zu", session_data->json_length);
|
||||
log_info(debug_msg);
|
||||
// Allocate buffer for JSON body transmission (no LWS_PRE needed for body)
|
||||
unsigned char *json_buf = malloc(session_data->json_length);
|
||||
if (!json_buf) {
|
||||
@@ -208,7 +201,6 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
lws_set_wsi_user(wsi, NULL);
|
||||
return -1;
|
||||
}
|
||||
log_info("NIP-11: Buffer allocated successfully");
|
||||
|
||||
// Copy JSON data to buffer
|
||||
memcpy(json_buf, session_data->json_buffer, session_data->json_length);
|
||||
@@ -278,24 +270,15 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
if (message) {
|
||||
memcpy(message, in, len);
|
||||
message[len] = '\0';
|
||||
|
||||
|
||||
// Parse JSON message (this is the normal program flow)
|
||||
cJSON* json = cJSON_Parse(message);
|
||||
if (json && cJSON_IsArray(json)) {
|
||||
// Log the complete parsed JSON message once
|
||||
char* complete_message = cJSON_Print(json);
|
||||
if (complete_message) {
|
||||
char debug_msg[2048];
|
||||
snprintf(debug_msg, sizeof(debug_msg),
|
||||
"Received complete WebSocket message: %s", complete_message);
|
||||
log_info(debug_msg);
|
||||
free(complete_message);
|
||||
}
|
||||
// Get message type
|
||||
cJSON* type = cJSON_GetArrayItem(json, 0);
|
||||
if (type && cJSON_IsString(type)) {
|
||||
const char* msg_type = cJSON_GetStringValue(type);
|
||||
|
||||
|
||||
if (strcmp(msg_type, "EVENT") == 0) {
|
||||
// Extract event for kind-specific NIP-42 authentication check
|
||||
cJSON* event_obj = cJSON_GetArrayItem(json, 1);
|
||||
@@ -303,19 +286,13 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Extract event kind for kind-specific NIP-42 authentication check
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event_obj, "kind");
|
||||
int event_kind = kind_obj && cJSON_IsNumber(kind_obj) ? (int)cJSON_GetNumberValue(kind_obj) : -1;
|
||||
|
||||
|
||||
// Extract pubkey and event ID for debugging
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event_obj, "pubkey");
|
||||
cJSON* id_obj = cJSON_GetObjectItem(event_obj, "id");
|
||||
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : "unknown";
|
||||
const char* event_id = id_obj ? cJSON_GetStringValue(id_obj) : "unknown";
|
||||
|
||||
char debug_event_msg[512];
|
||||
snprintf(debug_event_msg, sizeof(debug_event_msg),
|
||||
"DEBUG EVENT: Processing kind %d event from pubkey %.16s... ID %.16s...",
|
||||
event_kind, event_pubkey, event_id);
|
||||
log_info(debug_event_msg);
|
||||
|
||||
|
||||
// Check if NIP-42 authentication is required for this event kind or globally
|
||||
int auth_required = is_nip42_auth_globally_required() || is_nip42_auth_required_for_kind(event_kind);
|
||||
|
||||
@@ -344,15 +321,8 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
}
|
||||
}
|
||||
|
||||
char debug_auth_msg[256];
|
||||
snprintf(debug_auth_msg, sizeof(debug_auth_msg),
|
||||
"DEBUG AUTH: auth_required=%d, bypass_auth=%d, pss->authenticated=%d, event_kind=%d",
|
||||
auth_required, bypass_auth, pss ? pss->authenticated : -1, event_kind);
|
||||
log_info(debug_auth_msg);
|
||||
|
||||
if (pss && auth_required && !pss->authenticated && !bypass_auth) {
|
||||
if (!pss->auth_challenge_sent) {
|
||||
log_info("DEBUG AUTH: Sending NIP-42 authentication challenge");
|
||||
send_nip42_auth_challenge(wsi, pss);
|
||||
} else {
|
||||
char auth_msg[256];
|
||||
@@ -365,9 +335,6 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
}
|
||||
send_notice_message(wsi, auth_msg);
|
||||
log_warning("Event rejected: NIP-42 authentication required for kind");
|
||||
char debug_msg[128];
|
||||
snprintf(debug_msg, sizeof(debug_msg), "Auth required for kind %d", event_kind);
|
||||
log_info(debug_msg);
|
||||
}
|
||||
cJSON_Delete(json);
|
||||
free(message);
|
||||
@@ -448,20 +415,13 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_info("DEBUG VALIDATION: Starting unified validator");
|
||||
|
||||
// Call unified validator with JSON string
|
||||
size_t event_json_len = strlen(event_json_str);
|
||||
int validation_result = nostr_validate_unified_request(event_json_str, event_json_len);
|
||||
|
||||
|
||||
// Map validation result to old result format (0 = success, -1 = failure)
|
||||
int result = (validation_result == NOSTR_SUCCESS) ? 0 : -1;
|
||||
|
||||
char debug_validation_msg[256];
|
||||
snprintf(debug_validation_msg, sizeof(debug_validation_msg),
|
||||
"DEBUG VALIDATION: validation_result=%d, result=%d", validation_result, result);
|
||||
log_info(debug_validation_msg);
|
||||
|
||||
|
||||
// Generate error message based on validation result
|
||||
char error_message[512] = {0};
|
||||
if (result != 0) {
|
||||
@@ -491,12 +451,6 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
strncpy(error_message, "error: validation failed", sizeof(error_message) - 1);
|
||||
break;
|
||||
}
|
||||
char debug_error_msg[256];
|
||||
snprintf(debug_error_msg, sizeof(debug_error_msg),
|
||||
"DEBUG VALIDATION ERROR: %s", error_message);
|
||||
log_warning(debug_error_msg);
|
||||
} else {
|
||||
log_info("DEBUG VALIDATION: Event validated successfully using unified validator");
|
||||
}
|
||||
|
||||
// Cleanup event JSON string
|
||||
@@ -543,8 +497,6 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
strncpy(error_message, "auth-required: protected event requires authentication", sizeof(error_message) - 1);
|
||||
error_message[sizeof(error_message) - 1] = '\0';
|
||||
log_warning("Protected event rejected: authentication required");
|
||||
} else {
|
||||
log_info("Protected event accepted: authenticated publisher");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -555,55 +507,33 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
if (kind_obj && cJSON_IsNumber(kind_obj)) {
|
||||
int event_kind = (int)cJSON_GetNumberValue(kind_obj);
|
||||
|
||||
log_info("DEBUG ADMIN: Checking if admin event processing is needed");
|
||||
|
||||
|
||||
// Log reception of Kind 23456 events
|
||||
if (event_kind == 23456) {
|
||||
char* event_json_debug = cJSON_Print(event);
|
||||
char debug_received_msg[1024];
|
||||
snprintf(debug_received_msg, sizeof(debug_received_msg),
|
||||
"RECEIVED Kind %d event: %s", event_kind,
|
||||
event_json_debug ? event_json_debug : "Failed to serialize");
|
||||
log_info(debug_received_msg);
|
||||
|
||||
if (event_json_debug) {
|
||||
free(event_json_debug);
|
||||
}
|
||||
log_info("Received admin event (kind 23456)");
|
||||
}
|
||||
|
||||
if (event_kind == 23456) {
|
||||
// Enhanced admin event security - check authorization first
|
||||
log_info("DEBUG ADMIN: Admin event detected, checking authorization");
|
||||
|
||||
|
||||
char auth_error[512] = {0};
|
||||
int auth_result = is_authorized_admin_event(event, auth_error, sizeof(auth_error));
|
||||
|
||||
if (auth_result != 0) {
|
||||
// Authorization failed - log and reject
|
||||
log_warning("DEBUG ADMIN: Admin event authorization failed");
|
||||
log_warning("Admin event authorization failed");
|
||||
result = -1;
|
||||
size_t error_len = strlen(auth_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
memcpy(error_message, auth_error, copy_len);
|
||||
error_message[copy_len] = '\0';
|
||||
|
||||
char debug_auth_error_msg[600];
|
||||
snprintf(debug_auth_error_msg, sizeof(debug_auth_error_msg),
|
||||
"DEBUG ADMIN AUTH ERROR: %.400s", auth_error);
|
||||
log_warning(debug_auth_error_msg);
|
||||
} else {
|
||||
// Authorization successful - process through admin API
|
||||
log_info("DEBUG ADMIN: Admin event authorized, processing through admin API");
|
||||
|
||||
log_info("Processing authorized admin event");
|
||||
|
||||
char admin_error[512] = {0};
|
||||
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
|
||||
|
||||
char debug_admin_msg[256];
|
||||
snprintf(debug_admin_msg, sizeof(debug_admin_msg),
|
||||
"DEBUG ADMIN: process_admin_event_in_config returned %d", admin_result);
|
||||
log_info(debug_admin_msg);
|
||||
|
||||
// Log results for Kind 23456 events
|
||||
if (event_kind == 23456) {
|
||||
if (admin_result == 0) {
|
||||
@@ -620,65 +550,60 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
}
|
||||
|
||||
if (admin_result != 0) {
|
||||
log_error("DEBUG ADMIN: Failed to process admin event through admin API");
|
||||
log_error("Failed to process admin event");
|
||||
result = -1;
|
||||
size_t error_len = strlen(admin_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
memcpy(error_message, admin_error, copy_len);
|
||||
error_message[copy_len] = '\0';
|
||||
|
||||
char debug_admin_error_msg[600];
|
||||
snprintf(debug_admin_error_msg, sizeof(debug_admin_error_msg),
|
||||
"DEBUG ADMIN ERROR: %.400s", admin_error);
|
||||
log_error(debug_admin_error_msg);
|
||||
} else {
|
||||
log_success("DEBUG ADMIN: Admin event processed successfully through admin API");
|
||||
log_success("Admin event processed successfully");
|
||||
// Admin events are processed by the admin API, not broadcast to subscriptions
|
||||
}
|
||||
}
|
||||
} else if (event_kind == 1059) {
|
||||
// Check for NIP-17 gift wrap admin messages
|
||||
log_info("DEBUG NIP17: Detected kind 1059 gift wrap event");
|
||||
log_info("Processing NIP-17 gift wrap event");
|
||||
|
||||
char nip17_error[512] = {0};
|
||||
cJSON* response_event = process_nip17_admin_message(event, nip17_error, sizeof(nip17_error), wsi);
|
||||
|
||||
if (!response_event) {
|
||||
log_error("DEBUG NIP17: NIP-17 admin message processing failed");
|
||||
result = -1;
|
||||
size_t error_len = strlen(nip17_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
memcpy(error_message, nip17_error, copy_len);
|
||||
error_message[copy_len] = '\0';
|
||||
// Check if this is an error or if the command was already handled
|
||||
if (strlen(nip17_error) > 0) {
|
||||
// There was an actual error
|
||||
log_error("NIP-17 admin message processing failed");
|
||||
result = -1;
|
||||
size_t error_len = strlen(nip17_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
memcpy(error_message, nip17_error, copy_len);
|
||||
error_message[copy_len] = '\0';
|
||||
|
||||
char debug_nip17_error_msg[600];
|
||||
snprintf(debug_nip17_error_msg, sizeof(debug_nip17_error_msg),
|
||||
"DEBUG NIP17 ERROR: %.400s", nip17_error);
|
||||
log_error(debug_nip17_error_msg);
|
||||
} else {
|
||||
// No error message means the command was already handled (plain text commands)
|
||||
log_success("NIP-17 admin message processed successfully");
|
||||
// Store the original gift wrap event in database
|
||||
if (store_event(event) != 0) {
|
||||
log_error("Failed to store gift wrap event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store gift wrap event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
log_info("Gift wrap event stored successfully in database");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log_success("DEBUG NIP17: NIP-17 admin message processed successfully");
|
||||
log_success("NIP-17 admin message processed successfully");
|
||||
// Store the original gift wrap event in database (unlike kind 23456)
|
||||
if (store_event(event) != 0) {
|
||||
log_error("DEBUG NIP17: Failed to store gift wrap event in database");
|
||||
log_error("Failed to store gift wrap event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store gift wrap event", sizeof(error_message) - 1);
|
||||
cJSON_Delete(response_event);
|
||||
} else {
|
||||
log_info("DEBUG NIP17: Gift wrap event stored successfully in database");
|
||||
// Debug: Print response event before broadcasting
|
||||
char* debug_before_broadcast = cJSON_Print(response_event);
|
||||
if (debug_before_broadcast) {
|
||||
log_info("DEBUG EVENT: Before broadcasting response event");
|
||||
printf(" Response Event: %s\n", debug_before_broadcast);
|
||||
free(debug_before_broadcast);
|
||||
}
|
||||
log_info("Gift wrap event stored successfully in database");
|
||||
|
||||
// Broadcast RESPONSE event to matching persistent subscriptions
|
||||
int broadcast_count = broadcast_event_to_subscriptions(response_event);
|
||||
char debug_broadcast_msg[128];
|
||||
snprintf(debug_broadcast_msg, sizeof(debug_broadcast_msg),
|
||||
"DEBUG NIP17 BROADCAST: Response event broadcast to %d subscriptions", broadcast_count);
|
||||
log_info(debug_broadcast_msg);
|
||||
broadcast_event_to_subscriptions(response_event);
|
||||
|
||||
// Clean up response event
|
||||
cJSON_Delete(response_event);
|
||||
@@ -686,66 +611,54 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
}
|
||||
} else if (event_kind == 14) {
|
||||
// Check for DM stats commands addressed to relay
|
||||
log_info("DEBUG DM: Detected kind 14 DM event");
|
||||
log_info("Processing DM event");
|
||||
|
||||
char dm_error[512] = {0};
|
||||
int dm_result = process_dm_stats_command(event, dm_error, sizeof(dm_error), wsi);
|
||||
|
||||
if (dm_result != 0) {
|
||||
log_error("DEBUG DM: DM stats command processing failed");
|
||||
log_error("DM stats command processing failed");
|
||||
result = -1;
|
||||
size_t error_len = strlen(dm_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
memcpy(error_message, dm_error, copy_len);
|
||||
error_message[copy_len] = '\0';
|
||||
|
||||
char debug_dm_error_msg[600];
|
||||
snprintf(debug_dm_error_msg, sizeof(debug_dm_error_msg),
|
||||
"DEBUG DM ERROR: %.400s", dm_error);
|
||||
log_error(debug_dm_error_msg);
|
||||
} else {
|
||||
log_success("DEBUG DM: DM stats command processed successfully");
|
||||
log_success("DM stats command processed successfully");
|
||||
// Store the DM event in database
|
||||
if (store_event(event) != 0) {
|
||||
log_error("DEBUG DM: Failed to store DM event in database");
|
||||
log_error("Failed to store DM event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store DM event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
log_info("DEBUG DM: DM event stored successfully in database");
|
||||
log_info("DM event stored successfully in database");
|
||||
// Broadcast DM event to matching persistent subscriptions
|
||||
int broadcast_count = broadcast_event_to_subscriptions(event);
|
||||
char debug_broadcast_msg[128];
|
||||
snprintf(debug_broadcast_msg, sizeof(debug_broadcast_msg),
|
||||
"DEBUG DM BROADCAST: DM event broadcast to %d subscriptions", broadcast_count);
|
||||
log_info(debug_broadcast_msg);
|
||||
broadcast_event_to_subscriptions(event);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Regular event - store in database and broadcast
|
||||
log_info("DEBUG STORAGE: Regular event - storing in database");
|
||||
log_info("Storing regular event in database");
|
||||
if (store_event(event) != 0) {
|
||||
log_error("DEBUG STORAGE: Failed to store event in database");
|
||||
log_error("Failed to store event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
log_info("DEBUG STORAGE: Event stored successfully in database");
|
||||
log_info("Event stored successfully in database");
|
||||
// Broadcast event to matching persistent subscriptions
|
||||
int broadcast_count = broadcast_event_to_subscriptions(event);
|
||||
char debug_broadcast_msg[128];
|
||||
snprintf(debug_broadcast_msg, sizeof(debug_broadcast_msg),
|
||||
"DEBUG BROADCAST: Event broadcast to %d subscriptions", broadcast_count);
|
||||
log_info(debug_broadcast_msg);
|
||||
broadcast_event_to_subscriptions(event);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Event without valid kind - try normal storage
|
||||
log_warning("DEBUG STORAGE: Event without valid kind - trying normal storage");
|
||||
log_warning("Event without valid kind - trying normal storage");
|
||||
if (store_event(event) != 0) {
|
||||
log_error("DEBUG STORAGE: Failed to store event without kind in database");
|
||||
log_error("Failed to store event without kind in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
log_info("DEBUG STORAGE: Event without kind stored successfully in database");
|
||||
log_info("Event without kind stored successfully in database");
|
||||
broadcast_event_to_subscriptions(event);
|
||||
}
|
||||
}
|
||||
@@ -760,25 +673,13 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
cJSON_AddItemToArray(response, cJSON_CreateBool(result == 0));
|
||||
cJSON_AddItemToArray(response, cJSON_CreateString(strlen(error_message) > 0 ? error_message : ""));
|
||||
|
||||
// TODO: REPLACE - Remove wasteful cJSON_Print conversion
|
||||
char *response_str = cJSON_Print(response);
|
||||
if (response_str) {
|
||||
char debug_response_msg[512];
|
||||
snprintf(debug_response_msg, sizeof(debug_response_msg),
|
||||
"DEBUG RESPONSE: Sending OK response: %s", response_str);
|
||||
log_info(debug_response_msg);
|
||||
|
||||
size_t response_len = strlen(response_str);
|
||||
unsigned char *buf = malloc(LWS_PRE + response_len);
|
||||
if (buf) {
|
||||
memcpy(buf + LWS_PRE, response_str, response_len);
|
||||
int write_result = lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
|
||||
|
||||
char debug_write_msg[128];
|
||||
snprintf(debug_write_msg, sizeof(debug_write_msg),
|
||||
"DEBUG RESPONSE: lws_write returned %d", write_result);
|
||||
log_info(debug_write_msg);
|
||||
|
||||
lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
|
||||
free(buf);
|
||||
}
|
||||
free(response_str);
|
||||
@@ -1138,7 +1039,7 @@ int start_websocket_relay(int port_override, int strict_port) {
|
||||
log_success(startup_msg);
|
||||
|
||||
// Main event loop with proper signal handling
|
||||
while (g_server_running) {
|
||||
while (g_server_running && !g_shutdown_flag) {
|
||||
int result = lws_service(ws_context, 1000);
|
||||
|
||||
if (result < 0) {
|
||||
|
||||
Reference in New Issue
Block a user