Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00a8f16262 | ||
|
|
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 init-db # Set up database"
|
||||||
@echo " make force-version # Force regenerate main.h from git"
|
@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
|
.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
|
# 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
|
## 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-50: Keywords filter
|
||||||
- [x] NIP-70: Protected Events
|
- [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
|
## Web Admin Interface
|
||||||
|
|
||||||
C-Relay includes a **built-in web-based administration interface** accessible at `http://localhost:8888/api/`. The interface provides:
|
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.
|
The web interface serves embedded static files with no external dependencies and includes proper CORS headers for browser compatibility.
|
||||||
|
|
||||||
|
|
||||||
## Administrator API
|
## 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.
|
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.
|
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">
|
<div class="inline-buttons">
|
||||||
<button type="button" id="connect-relay-btn">CONNECT TO RELAY</button>
|
<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="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>
|
||||||
|
|
||||||
<div class="status disconnected" id="relay-connection-status">NOT CONNECTED</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 relayConnectionStatus = document.getElementById('relay-connection-status');
|
||||||
const connectRelayBtn = document.getElementById('connect-relay-btn');
|
const connectRelayBtn = document.getElementById('connect-relay-btn');
|
||||||
const disconnectRelayBtn = document.getElementById('disconnect-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 configDisplay = document.getElementById('config-display');
|
||||||
const configTableBody = document.getElementById('config-table-body');
|
const configTableBody = document.getElementById('config-table-body');
|
||||||
|
|
||||||
@@ -369,28 +369,28 @@
|
|||||||
relayConnectionStatus.className = 'status connected';
|
relayConnectionStatus.className = 'status connected';
|
||||||
connectRelayBtn.disabled = true;
|
connectRelayBtn.disabled = true;
|
||||||
disconnectRelayBtn.disabled = true;
|
disconnectRelayBtn.disabled = true;
|
||||||
testWebSocketBtn.disabled = true;
|
restartRelayBtn.disabled = true;
|
||||||
break;
|
break;
|
||||||
case 'connected':
|
case 'connected':
|
||||||
relayConnectionStatus.textContent = 'CONNECTED';
|
relayConnectionStatus.textContent = 'CONNECTED';
|
||||||
relayConnectionStatus.className = 'status connected';
|
relayConnectionStatus.className = 'status connected';
|
||||||
connectRelayBtn.disabled = true;
|
connectRelayBtn.disabled = true;
|
||||||
disconnectRelayBtn.disabled = false;
|
disconnectRelayBtn.disabled = false;
|
||||||
testWebSocketBtn.disabled = false;
|
restartRelayBtn.disabled = false;
|
||||||
break;
|
break;
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
relayConnectionStatus.textContent = 'NOT CONNECTED';
|
relayConnectionStatus.textContent = 'NOT CONNECTED';
|
||||||
relayConnectionStatus.className = 'status disconnected';
|
relayConnectionStatus.className = 'status disconnected';
|
||||||
connectRelayBtn.disabled = false;
|
connectRelayBtn.disabled = false;
|
||||||
disconnectRelayBtn.disabled = true;
|
disconnectRelayBtn.disabled = true;
|
||||||
testWebSocketBtn.disabled = true;
|
restartRelayBtn.disabled = true;
|
||||||
break;
|
break;
|
||||||
case 'error':
|
case 'error':
|
||||||
relayConnectionStatus.textContent = 'CONNECTION FAILED';
|
relayConnectionStatus.textContent = 'CONNECTION FAILED';
|
||||||
relayConnectionStatus.className = 'status error';
|
relayConnectionStatus.className = 'status error';
|
||||||
connectRelayBtn.disabled = false;
|
connectRelayBtn.disabled = false;
|
||||||
disconnectRelayBtn.disabled = true;
|
disconnectRelayBtn.disabled = true;
|
||||||
testWebSocketBtn.disabled = true;
|
restartRelayBtn.disabled = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1670,22 +1670,12 @@
|
|||||||
disconnectFromRelay();
|
disconnectFromRelay();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWebSocketBtn.addEventListener('click', function (e) {
|
restartRelayBtn.addEventListener('click', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const url = relayConnectionUrl.value.trim();
|
sendRestartCommand().catch(error => {
|
||||||
if (!url) {
|
log(`Restart command failed: ${error.message}`, 'ERROR');
|
||||||
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');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
@@ -3078,10 +3068,14 @@
|
|||||||
messageDiv.className = 'log-entry';
|
messageDiv.className = 'log-entry';
|
||||||
|
|
||||||
const directionColor = direction === 'sent' ? '#007bff' : '#28a745';
|
const directionColor = direction === 'sent' ? '#007bff' : '#28a745';
|
||||||
|
|
||||||
|
// Convert newlines to <br> tags for proper HTML display
|
||||||
|
const formattedMessage = message.replace(/\n/g, '<br>');
|
||||||
|
|
||||||
messageDiv.innerHTML = `
|
messageDiv.innerHTML = `
|
||||||
<span class="log-timestamp">${timestamp}</span>
|
<span class="log-timestamp">${timestamp}</span>
|
||||||
<span style="color: ${directionColor}; font-weight: bold;">[${direction.toUpperCase()}]</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
|
// Remove the "No messages received yet" placeholder if it exists
|
||||||
@@ -3144,6 +3138,83 @@
|
|||||||
// DATABASE STATISTICS FUNCTIONS
|
// 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)
|
// Send stats_query command to get database statistics using Administrator API (inner events)
|
||||||
async function sendStatsQuery() {
|
async function sendStatsQuery() {
|
||||||
if (!isLoggedIn || !userPubkey) {
|
if (!isLoggedIn || !userPubkey) {
|
||||||
@@ -3300,9 +3371,12 @@
|
|||||||
const events7d = document.getElementById('events-7d');
|
const events7d = document.getElementById('events-7d');
|
||||||
const events30d = document.getElementById('events-30d');
|
const events30d = document.getElementById('events-30d');
|
||||||
|
|
||||||
if (events24h) events24h.textContent = data.events_24h || '-';
|
// Access the nested time_stats object from backend response
|
||||||
if (events7d) events7d.textContent = data.events_7d || '-';
|
const timeStats = data.time_stats || {};
|
||||||
if (events30d) events30d.textContent = data.events_30d || '-';
|
|
||||||
|
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
|
// Populate top pubkeys table
|
||||||
|
|||||||
@@ -17,6 +17,33 @@ print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|||||||
COMMIT_MESSAGE=""
|
COMMIT_MESSAGE=""
|
||||||
RELEASE_MODE=false
|
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
|
# Parse command line arguments
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
@@ -38,32 +65,6 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
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
|
# Validate inputs
|
||||||
if [[ -z "$COMMIT_MESSAGE" ]]; then
|
if [[ -z "$COMMIT_MESSAGE" ]]; then
|
||||||
print_error "Commit message is required"
|
print_error "Commit message is required"
|
||||||
@@ -190,6 +191,35 @@ build_release_binaries() {
|
|||||||
print_status "Only x86_64 binary will be included in release"
|
print_status "Only x86_64 binary will be included in release"
|
||||||
fi
|
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
|
# Restore normal build
|
||||||
make clean > /dev/null 2>&1
|
make clean > /dev/null 2>&1
|
||||||
make > /dev/null 2>&1
|
make > /dev/null 2>&1
|
||||||
@@ -319,12 +349,18 @@ create_gitea_release() {
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{\"tag_name\": \"$NEW_VERSION\", \"name\": \"$NEW_VERSION\", \"body\": \"$COMMIT_MESSAGE\"}")
|
-d "{\"tag_name\": \"$NEW_VERSION\", \"name\": \"$NEW_VERSION\", \"body\": \"$COMMIT_MESSAGE\"}")
|
||||||
|
|
||||||
|
local upload_result=false
|
||||||
|
|
||||||
if echo "$response" | grep -q '"id"'; then
|
if echo "$response" | grep -q '"id"'; then
|
||||||
print_success "Created release $NEW_VERSION"
|
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
|
elif echo "$response" | grep -q "already exists"; then
|
||||||
print_warning "Release $NEW_VERSION already exists"
|
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
|
else
|
||||||
print_error "Failed to create release $NEW_VERSION"
|
print_error "Failed to create release $NEW_VERSION"
|
||||||
print_error "Response: $response"
|
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")
|
local check_response=$(curl -s -H "Authorization: token $token" "$api_url/releases/tags/$NEW_VERSION")
|
||||||
if echo "$check_response" | grep -q '"id"'; then
|
if echo "$check_response" | grep -q '"id"'; then
|
||||||
print_warning "Release exists but creation response was unexpected"
|
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
|
else
|
||||||
print_error "Release does not exist and creation failed"
|
print_error "Release does not exist and creation failed"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
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
|
# Function to upload release binaries
|
||||||
upload_release_binaries() {
|
upload_release_binaries() {
|
||||||
local api_url="$1"
|
local api_url="$1"
|
||||||
local token="$2"
|
local token="$2"
|
||||||
|
local upload_success=true
|
||||||
|
|
||||||
# Get release ID with more robust parsing
|
# Get release ID with more robust parsing
|
||||||
print_status "Getting release ID for $NEW_VERSION..."
|
print_status "Getting release ID for $NEW_VERSION..."
|
||||||
@@ -367,37 +414,131 @@ upload_release_binaries() {
|
|||||||
# Upload x86_64 binary
|
# Upload x86_64 binary
|
||||||
if [[ -f "c-relay-x86_64" ]]; then
|
if [[ -f "c-relay-x86_64" ]]; then
|
||||||
print_status "Uploading x86_64 binary..."
|
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" \
|
-H "Authorization: token $token" \
|
||||||
-F "attachment=@c-relay-x86_64;filename=c-relay-${NEW_VERSION}-linux-x86_64" > /dev/null; then
|
-F "attachment=@c-relay-x86_64;filename=c-relay-${NEW_VERSION}-linux-x86_64")
|
||||||
print_success "Uploaded x86_64 binary"
|
|
||||||
|
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
|
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
|
fi
|
||||||
|
else
|
||||||
|
print_warning "x86_64 binary not found: c-relay-x86_64"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Upload ARM64 binary
|
# Upload ARM64 binary
|
||||||
if [[ -f "c-relay-arm64" ]]; then
|
if [[ -f "c-relay-arm64" ]]; then
|
||||||
print_status "Uploading ARM64 binary..."
|
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" \
|
-H "Authorization: token $token" \
|
||||||
-F "attachment=@c-relay-arm64;filename=c-relay-${NEW_VERSION}-linux-arm64" > /dev/null; then
|
-F "attachment=@c-relay-arm64;filename=c-relay-${NEW_VERSION}-linux-arm64")
|
||||||
print_success "Uploaded ARM64 binary"
|
|
||||||
|
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
|
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
|
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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to clean up release binaries
|
# Function to clean up release binaries
|
||||||
cleanup_release_binaries() {
|
cleanup_release_binaries() {
|
||||||
if [[ -f "c-relay-x86_64" ]]; then
|
local force_cleanup="$1" # Optional parameter to force cleanup even on failure
|
||||||
rm -f c-relay-x86_64
|
|
||||||
print_status "Cleaned up x86_64 binary"
|
if [[ "$force_cleanup" == "force" ]] || [[ "$upload_success" == true ]]; then
|
||||||
fi
|
if [[ -f "c-relay-x86_64" ]]; then
|
||||||
if [[ -f "c-relay-arm64" ]]; then
|
rm -f c-relay-x86_64
|
||||||
rm -f c-relay-arm64
|
print_status "Cleaned up x86_64 binary"
|
||||||
print_status "Cleaned up ARM64 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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,14 +574,18 @@ main() {
|
|||||||
git_commit_and_push_no_tag
|
git_commit_and_push_no_tag
|
||||||
|
|
||||||
# Create Gitea release with binaries
|
# 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
|
cleanup_release_binaries
|
||||||
|
|
||||||
print_success "Release $NEW_VERSION completed successfully!"
|
|
||||||
print_status "Binaries uploaded to Gitea release"
|
|
||||||
|
|
||||||
else
|
else
|
||||||
print_status "=== DEFAULT MODE ==="
|
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)
|
// Handle HTTP request for embedded files (assumes GET)
|
||||||
int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
|
int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
|
||||||
log_info("Handling embedded file request");
|
|
||||||
|
|
||||||
const char* file_path;
|
const char* file_path;
|
||||||
|
|
||||||
// Handle /api requests
|
// Handle /api requests
|
||||||
@@ -124,7 +122,6 @@ int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
|
|||||||
// Request callback for body transmission
|
// Request callback for body transmission
|
||||||
lws_callback_on_writable(wsi);
|
lws_callback_on_writable(wsi);
|
||||||
|
|
||||||
log_success("Embedded file headers sent, body transmission scheduled");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +162,5 @@ int handle_embedded_file_writeable(struct lws* wsi) {
|
|||||||
free(session_data);
|
free(session_data);
|
||||||
lws_set_wsi_user(wsi, NULL);
|
lws_set_wsi_user(wsi, NULL);
|
||||||
|
|
||||||
log_success("Embedded file served successfully");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
400
src/config.c
400
src/config.c
File diff suppressed because it is too large
Load Diff
1498
src/dm_admin.c
1498
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);
|
int is_nip17_gift_wrap_for_relay(cJSON* gift_wrap_event);
|
||||||
char* generate_stats_json(void);
|
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
|
#endif // DM_ADMIN_H
|
||||||
File diff suppressed because one or more lines are too long
171
src/main.c
171
src/main.c
@@ -19,6 +19,7 @@
|
|||||||
#include "../nostr_core_lib/cjson/cJSON.h"
|
#include "../nostr_core_lib/cjson/cJSON.h"
|
||||||
#include "../nostr_core_lib/nostr_core/nostr_core.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/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 "config.h" // Configuration management system
|
||||||
#include "sql_schema.h" // Embedded database schema
|
#include "sql_schema.h" // Embedded database schema
|
||||||
#include "websockets.h" // WebSocket protocol implementation
|
#include "websockets.h" // WebSocket protocol implementation
|
||||||
@@ -45,6 +46,8 @@ int nostr_nip42_verify_auth_event(cJSON *event, const char *challenge_id,
|
|||||||
// Global state
|
// Global state
|
||||||
sqlite3* g_db = NULL; // Non-static so config.c can access it
|
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
|
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
|
struct lws_context *ws_context = NULL; // Non-static so websockets.c can access it
|
||||||
|
|
||||||
// NIP-11 relay information structure
|
// NIP-11 relay information structure
|
||||||
@@ -216,13 +219,11 @@ void update_subscription_manager_config(void) {
|
|||||||
"Subscription limits: max_per_client=%d, max_total=%d",
|
"Subscription limits: max_per_client=%d, max_total=%d",
|
||||||
g_subscription_manager.max_subscriptions_per_client,
|
g_subscription_manager.max_subscriptions_per_client,
|
||||||
g_subscription_manager.max_total_subscriptions);
|
g_subscription_manager.max_total_subscriptions);
|
||||||
log_info(config_msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signal handler for graceful shutdown
|
// Signal handler for graceful shutdown
|
||||||
void signal_handler(int sig) {
|
void signal_handler(int sig) {
|
||||||
if (sig == SIGINT || sig == SIGTERM) {
|
if (sig == SIGINT || sig == SIGTERM) {
|
||||||
log_info("Received shutdown signal");
|
|
||||||
g_server_running = 0;
|
g_server_running = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,10 +285,6 @@ int init_database(const char* database_path_override) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
char success_msg[256];
|
|
||||||
snprintf(success_msg, sizeof(success_msg), "Database connection established: %s", db_path);
|
|
||||||
log_success(success_msg);
|
|
||||||
|
|
||||||
// Check if database is already initialized by looking for the events table
|
// Check if database is already initialized by looking for the events table
|
||||||
const char* check_sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='events'";
|
const char* check_sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='events'";
|
||||||
sqlite3_stmt* check_stmt;
|
sqlite3_stmt* check_stmt;
|
||||||
@@ -297,59 +294,48 @@ int init_database(const char* database_path_override) {
|
|||||||
sqlite3_finalize(check_stmt);
|
sqlite3_finalize(check_stmt);
|
||||||
|
|
||||||
if (has_events_table) {
|
if (has_events_table) {
|
||||||
log_info("Database schema already exists, checking version");
|
|
||||||
|
|
||||||
// Check existing schema version and migrate if needed
|
// Check existing schema version and migrate if needed
|
||||||
const char* version_sql = "SELECT value FROM schema_info WHERE key = 'version'";
|
const char* version_sql = "SELECT value FROM schema_info WHERE key = 'version'";
|
||||||
sqlite3_stmt* version_stmt;
|
sqlite3_stmt* version_stmt;
|
||||||
const char* db_version = NULL;
|
const char* db_version = NULL;
|
||||||
int needs_migration = 0;
|
int needs_migration = 0;
|
||||||
|
|
||||||
if (sqlite3_prepare_v2(g_db, version_sql, -1, &version_stmt, NULL) == SQLITE_OK) {
|
if (sqlite3_prepare_v2(g_db, version_sql, -1, &version_stmt, NULL) == SQLITE_OK) {
|
||||||
if (sqlite3_step(version_stmt) == SQLITE_ROW) {
|
if (sqlite3_step(version_stmt) == SQLITE_ROW) {
|
||||||
db_version = (char*)sqlite3_column_text(version_stmt, 0);
|
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
|
// Check if migration is needed
|
||||||
if (!db_version || strcmp(db_version, "5") == 0) {
|
if (!db_version || strcmp(db_version, "5") == 0) {
|
||||||
needs_migration = 1;
|
needs_migration = 1;
|
||||||
log_info("Database migration needed: v5 -> v6 (adding auth_rules table)");
|
|
||||||
} else if (strcmp(db_version, "6") == 0) {
|
} 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) {
|
} else if (strcmp(db_version, EMBEDDED_SCHEMA_VERSION) == 0) {
|
||||||
log_info("Database is at current schema version");
|
// Database is at current schema version
|
||||||
} else {
|
} else {
|
||||||
char warning_msg[256];
|
char warning_msg[256];
|
||||||
snprintf(warning_msg, sizeof(warning_msg), "Unknown database schema version: %s", db_version);
|
snprintf(warning_msg, sizeof(warning_msg), "Unknown database schema version: %s", db_version);
|
||||||
log_warning(warning_msg);
|
log_warning(warning_msg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log_info("Database exists but no version information found, assuming migration needed");
|
|
||||||
needs_migration = 1;
|
needs_migration = 1;
|
||||||
}
|
}
|
||||||
sqlite3_finalize(version_stmt);
|
sqlite3_finalize(version_stmt);
|
||||||
} else {
|
} else {
|
||||||
log_info("Cannot read schema version, assuming migration needed");
|
|
||||||
needs_migration = 1;
|
needs_migration = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform migration if needed
|
// Perform migration if needed
|
||||||
if (needs_migration) {
|
if (needs_migration) {
|
||||||
log_info("Performing database schema migration to v6");
|
|
||||||
|
|
||||||
// Check if auth_rules table already exists
|
// 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'";
|
const char* check_auth_rules_sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='auth_rules'";
|
||||||
sqlite3_stmt* check_stmt;
|
sqlite3_stmt* check_stmt;
|
||||||
int has_auth_rules = 0;
|
int has_auth_rules = 0;
|
||||||
|
|
||||||
if (sqlite3_prepare_v2(g_db, check_auth_rules_sql, -1, &check_stmt, NULL) == SQLITE_OK) {
|
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);
|
has_auth_rules = (sqlite3_step(check_stmt) == SQLITE_ROW);
|
||||||
sqlite3_finalize(check_stmt);
|
sqlite3_finalize(check_stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!has_auth_rules) {
|
if (!has_auth_rules) {
|
||||||
// Add auth_rules table matching sql_schema.h
|
// Add auth_rules table matching sql_schema.h
|
||||||
const char* create_auth_rules_sql =
|
const char* create_auth_rules_sql =
|
||||||
@@ -375,7 +361,6 @@ int init_database(const char* database_path_override) {
|
|||||||
if (error_msg) sqlite3_free(error_msg);
|
if (error_msg) sqlite3_free(error_msg);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
log_success("Created auth_rules table");
|
|
||||||
|
|
||||||
// Add indexes for auth_rules table
|
// Add indexes for auth_rules table
|
||||||
const char* create_auth_rules_indexes_sql =
|
const char* create_auth_rules_indexes_sql =
|
||||||
@@ -393,9 +378,8 @@ int init_database(const char* database_path_override) {
|
|||||||
if (index_error_msg) sqlite3_free(index_error_msg);
|
if (index_error_msg) sqlite3_free(index_error_msg);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
log_success("Created auth_rules indexes");
|
|
||||||
} else {
|
} else {
|
||||||
log_info("auth_rules table already exists, skipping creation");
|
// auth_rules table already exists, skipping creation
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update schema version to v6
|
// Update schema version to v6
|
||||||
@@ -414,12 +398,10 @@ int init_database(const char* database_path_override) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
log_success("Database migration to v6 completed successfully");
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Initialize database schema using embedded SQL
|
// Initialize database schema using embedded SQL
|
||||||
log_info("Initializing database schema from embedded SQL");
|
|
||||||
|
|
||||||
// Execute the embedded schema SQL
|
// Execute the embedded schema SQL
|
||||||
char* error_msg = NULL;
|
char* error_msg = NULL;
|
||||||
rc = sqlite3_exec(g_db, EMBEDDED_SCHEMA_SQL, NULL, NULL, &error_msg);
|
rc = sqlite3_exec(g_db, EMBEDDED_SCHEMA_SQL, NULL, NULL, &error_msg);
|
||||||
@@ -434,13 +416,6 @@ int init_database(const char* database_path_override) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
log_error("Failed to check existing database schema");
|
log_error("Failed to check existing database schema");
|
||||||
@@ -455,7 +430,6 @@ void close_database() {
|
|||||||
if (g_db) {
|
if (g_db) {
|
||||||
sqlite3_close(g_db);
|
sqlite3_close(g_db);
|
||||||
g_db = NULL;
|
g_db = NULL;
|
||||||
log_info("Database connection closed");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,7 +660,6 @@ int store_event(cJSON* event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
free(tags_json);
|
free(tags_json);
|
||||||
log_success("Event stored in database");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -749,8 +722,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) {
|
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)) {
|
if (!cJSON_IsArray(filters)) {
|
||||||
log_error("REQ filters is not an array");
|
log_error("REQ filters is not an array");
|
||||||
return 0;
|
return 0;
|
||||||
@@ -791,11 +762,6 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
|||||||
|
|
||||||
cJSON_Delete(filters_array);
|
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
|
break; // Only generate once per subscription
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1072,12 +1038,7 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
|||||||
// Default limit to prevent excessive queries
|
// Default limit to prevent excessive queries
|
||||||
snprintf(sql_ptr, remaining, " LIMIT 500");
|
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
|
// Execute query and send events
|
||||||
sqlite3_stmt* stmt;
|
sqlite3_stmt* stmt;
|
||||||
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
int rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
||||||
@@ -1122,11 +1083,6 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
|||||||
time_t current_time = time(NULL);
|
time_t current_time = time(NULL);
|
||||||
if (is_event_expired(event, current_time)) {
|
if (is_event_expired(event, current_time)) {
|
||||||
// Skip this expired event
|
// 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);
|
cJSON_Delete(event);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1153,18 +1109,10 @@ int handle_req_message(const char* sub_id, cJSON* filters, struct lws *wsi, stru
|
|||||||
cJSON_Delete(event_msg);
|
cJSON_Delete(event_msg);
|
||||||
events_sent++;
|
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);
|
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;
|
return events_sent;
|
||||||
}
|
}
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -1199,7 +1147,6 @@ int is_authorized_admin_event(cJSON* event, char* error_buffer, size_t error_buf
|
|||||||
cJSON *tags = cJSON_GetObjectItem(event, "tags");
|
cJSON *tags = cJSON_GetObjectItem(event, "tags");
|
||||||
if (!tags || !cJSON_IsArray(tags)) {
|
if (!tags || !cJSON_IsArray(tags)) {
|
||||||
// No tags array - treat as regular event for different relay
|
// No tags array - treat as regular event for different relay
|
||||||
log_info("Admin event has no tags array - treating as event for different relay");
|
|
||||||
snprintf(error_buffer, error_buffer_size, "Admin event not targeting this relay (no tags)");
|
snprintf(error_buffer, error_buffer_size, "Admin event not targeting this relay (no tags)");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -1227,7 +1174,6 @@ int is_authorized_admin_event(cJSON* event, char* error_buffer, size_t error_buf
|
|||||||
|
|
||||||
if (!targets_this_relay) {
|
if (!targets_this_relay) {
|
||||||
// Admin event for different relay - not an error, just not for us
|
// 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");
|
snprintf(error_buffer, error_buffer_size, "Admin event not targeting this relay");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -1267,7 +1213,6 @@ int is_authorized_admin_event(cJSON* event, char* error_buffer, size_t error_buf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// All checks passed - authorized admin event
|
// All checks passed - authorized admin event
|
||||||
log_info("Admin event authorization successful");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1291,8 +1236,8 @@ void print_usage(const char* program_name) {
|
|||||||
printf(" -v, --version Show version information\n");
|
printf(" -v, --version Show version information\n");
|
||||||
printf(" -p, --port PORT Override relay port (first-time startup only)\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(" --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(" -a, --admin-pubkey KEY Override admin public key (64-char hex or npub)\n");
|
||||||
printf(" -r, --relay-privkey HEX Override relay private key (64-char hex)\n");
|
printf(" -r, --relay-privkey KEY Override relay private key (64-char hex or nsec)\n");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("Configuration:\n");
|
printf("Configuration:\n");
|
||||||
printf(" This relay uses event-based configuration stored in the database.\n");
|
printf(" This relay uses event-based configuration stored in the database.\n");
|
||||||
@@ -1364,7 +1309,6 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
char port_msg[128];
|
char port_msg[128];
|
||||||
snprintf(port_msg, sizeof(port_msg), "Port override specified: %d", cli_options.port_override);
|
snprintf(port_msg, sizeof(port_msg), "Port override specified: %d", cli_options.port_override);
|
||||||
log_info(port_msg);
|
|
||||||
} else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--admin-pubkey") == 0) {
|
} else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--admin-pubkey") == 0) {
|
||||||
// Admin public key override option
|
// Admin public key override option
|
||||||
if (i + 1 >= argc) {
|
if (i + 1 >= argc) {
|
||||||
@@ -1373,28 +1317,28 @@ int main(int argc, char* argv[]) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate public key format (must be 64 hex characters)
|
const char* input_key = argv[i + 1];
|
||||||
if (strlen(argv[i + 1]) != 64) {
|
char decoded_key[65] = {0}; // Buffer for decoded hex key
|
||||||
log_error("Invalid admin public key length. Must be exactly 64 hex characters.");
|
|
||||||
|
// 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]);
|
print_usage(argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate hex format
|
strncpy(cli_options.admin_pubkey_override, decoded_key, sizeof(cli_options.admin_pubkey_override) - 1);
|
||||||
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);
|
|
||||||
cli_options.admin_pubkey_override[sizeof(cli_options.admin_pubkey_override) - 1] = '\0';
|
cli_options.admin_pubkey_override[sizeof(cli_options.admin_pubkey_override) - 1] = '\0';
|
||||||
i++; // Skip the key argument
|
i++; // Skip the key argument
|
||||||
|
|
||||||
log_info("Admin public key override specified");
|
|
||||||
} else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--relay-privkey") == 0) {
|
} else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--relay-privkey") == 0) {
|
||||||
// Relay private key override option
|
// Relay private key override option
|
||||||
if (i + 1 >= argc) {
|
if (i + 1 >= argc) {
|
||||||
@@ -1402,33 +1346,32 @@ int main(int argc, char* argv[]) {
|
|||||||
print_usage(argv[0]);
|
print_usage(argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate private key format (must be 64 hex characters)
|
const char* input_key = argv[i + 1];
|
||||||
if (strlen(argv[i + 1]) != 64) {
|
char decoded_key[65] = {0}; // Buffer for decoded hex key
|
||||||
log_error("Invalid relay private key length. Must be exactly 64 hex characters.");
|
|
||||||
|
// 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]);
|
print_usage(argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate hex format
|
strncpy(cli_options.relay_privkey_override, decoded_key, sizeof(cli_options.relay_privkey_override) - 1);
|
||||||
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);
|
|
||||||
cli_options.relay_privkey_override[sizeof(cli_options.relay_privkey_override) - 1] = '\0';
|
cli_options.relay_privkey_override[sizeof(cli_options.relay_privkey_override) - 1] = '\0';
|
||||||
i++; // Skip the key argument
|
i++; // Skip the key argument
|
||||||
|
|
||||||
log_info("Relay private key override specified");
|
|
||||||
} else if (strcmp(argv[i], "--strict-port") == 0) {
|
} else if (strcmp(argv[i], "--strict-port") == 0) {
|
||||||
// Strict port mode option
|
// Strict port mode option
|
||||||
cli_options.strict_port = 1;
|
cli_options.strict_port = 1;
|
||||||
log_info("Strict port mode enabled - will fail if exact port is unavailable");
|
|
||||||
} else {
|
} else {
|
||||||
log_error("Unknown argument. Use --help for usage information.");
|
log_error("Unknown argument. Use --help for usage information.");
|
||||||
print_usage(argv[0]);
|
print_usage(argv[0]);
|
||||||
@@ -1451,7 +1394,6 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
// Check if this is first-time startup or existing relay
|
// Check if this is first-time startup or existing relay
|
||||||
if (is_first_time_startup()) {
|
if (is_first_time_startup()) {
|
||||||
log_info("First-time startup detected");
|
|
||||||
|
|
||||||
// Initialize event-based configuration system
|
// Initialize event-based configuration system
|
||||||
if (init_configuration_system(NULL, NULL) != 0) {
|
if (init_configuration_system(NULL, NULL) != 0) {
|
||||||
@@ -1485,7 +1427,6 @@ int main(int argc, char* argv[]) {
|
|||||||
nostr_cleanup();
|
nostr_cleanup();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
log_success("Relay private key stored securely in database");
|
|
||||||
} else {
|
} else {
|
||||||
log_error("Relay private key not available from first-time startup");
|
log_error("Relay private key not available from first-time startup");
|
||||||
cleanup_configuration_system();
|
cleanup_configuration_system();
|
||||||
@@ -1495,7 +1436,6 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
// Handle configuration setup after database is initialized
|
// Handle configuration setup after database is initialized
|
||||||
// Always populate defaults directly in config table (abandoning legacy event signing)
|
// 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
|
// Populate default config values in table
|
||||||
if (populate_default_config_values() != 0) {
|
if (populate_default_config_values() != 0) {
|
||||||
@@ -1517,7 +1457,6 @@ int main(int argc, char* argv[]) {
|
|||||||
close_database();
|
close_database();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
log_info("Applied port override from command line");
|
|
||||||
printf(" Port: %d (overriding default)\n", cli_options.port_override);
|
printf(" Port: %d (overriding default)\n", cli_options.port_override);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1530,22 +1469,17 @@ int main(int argc, char* argv[]) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
log_success("Configuration populated directly in config table after database initialization");
|
|
||||||
|
|
||||||
// Now store the pubkeys in config table since database is available
|
// Now store the pubkeys in config table since database is available
|
||||||
const char* admin_pubkey = get_admin_pubkey_cached();
|
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||||
const char* relay_pubkey_from_cache = get_relay_pubkey_cached();
|
const char* relay_pubkey_from_cache = get_relay_pubkey_cached();
|
||||||
if (admin_pubkey && strlen(admin_pubkey) == 64) {
|
if (admin_pubkey && strlen(admin_pubkey) == 64) {
|
||||||
set_config_value_in_table("admin_pubkey", admin_pubkey, "string", "Administrator public key", "authentication", 0);
|
set_config_value_in_table("admin_pubkey", admin_pubkey, "string", "Administrator public key", "authentication", 0);
|
||||||
log_success("Admin pubkey stored in config table for first-time startup");
|
|
||||||
}
|
}
|
||||||
if (relay_pubkey_from_cache && strlen(relay_pubkey_from_cache) == 64) {
|
if (relay_pubkey_from_cache && strlen(relay_pubkey_from_cache) == 64) {
|
||||||
set_config_value_in_table("relay_pubkey", relay_pubkey_from_cache, "string", "Relay public key", "relay", 0);
|
set_config_value_in_table("relay_pubkey", relay_pubkey_from_cache, "string", "Relay public key", "relay", 0);
|
||||||
log_success("Relay pubkey stored in config table for first-time startup");
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log_info("Existing relay detected");
|
|
||||||
|
|
||||||
// Find existing database file
|
// Find existing database file
|
||||||
char** existing_files = find_existing_db_files();
|
char** existing_files = find_existing_db_files();
|
||||||
if (!existing_files || !existing_files[0]) {
|
if (!existing_files || !existing_files[0]) {
|
||||||
@@ -1611,7 +1545,6 @@ int main(int argc, char* argv[]) {
|
|||||||
if (apply_configuration_from_event(config_event) != 0) {
|
if (apply_configuration_from_event(config_event) != 0) {
|
||||||
log_warning("Failed to apply configuration from database");
|
log_warning("Failed to apply configuration from database");
|
||||||
} else {
|
} else {
|
||||||
log_success("Configuration loaded from database");
|
|
||||||
|
|
||||||
// Extract admin pubkey from the config event and store in config table for unified cache access
|
// Extract admin pubkey from the config event and store in config table for unified cache access
|
||||||
cJSON* pubkey_obj = cJSON_GetObjectItem(config_event, "pubkey");
|
cJSON* pubkey_obj = cJSON_GetObjectItem(config_event, "pubkey");
|
||||||
@@ -1620,12 +1553,10 @@ int main(int argc, char* argv[]) {
|
|||||||
// Store both admin and relay pubkeys in config table for unified cache
|
// Store both admin and relay pubkeys in config table for unified cache
|
||||||
if (admin_pubkey && strlen(admin_pubkey) == 64) {
|
if (admin_pubkey && strlen(admin_pubkey) == 64) {
|
||||||
set_config_value_in_table("admin_pubkey", admin_pubkey, "string", "Administrator public key", "authentication", 0);
|
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) {
|
if (relay_pubkey && strlen(relay_pubkey) == 64) {
|
||||||
set_config_value_in_table("relay_pubkey", relay_pubkey, "string", "Relay public key", "relay", 0);
|
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);
|
cJSON_Delete(config_event);
|
||||||
@@ -1644,7 +1575,6 @@ int main(int argc, char* argv[]) {
|
|||||||
close_database();
|
close_database();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
log_info("Applied port override from command line for existing relay");
|
|
||||||
printf(" Port: %d (overriding configured port)\n", cli_options.port_override);
|
printf(" Port: %d (overriding configured port)\n", cli_options.port_override);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1675,7 +1605,6 @@ int main(int argc, char* argv[]) {
|
|||||||
close_database();
|
close_database();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
log_success("Unified request validator initialized");
|
|
||||||
|
|
||||||
// Initialize NIP-11 relay information
|
// Initialize NIP-11 relay information
|
||||||
init_relay_info();
|
init_relay_info();
|
||||||
@@ -1689,7 +1618,6 @@ int main(int argc, char* argv[]) {
|
|||||||
// Update subscription manager configuration
|
// Update subscription manager configuration
|
||||||
update_subscription_manager_config();
|
update_subscription_manager_config();
|
||||||
|
|
||||||
log_info("Starting relay server...");
|
|
||||||
|
|
||||||
// Start WebSocket Nostr relay server (port from configuration)
|
// Start WebSocket Nostr relay server (port from configuration)
|
||||||
int result = start_websocket_relay(-1, cli_options.strict_port); // Let config system determine port, pass strict_port flag
|
int result = start_websocket_relay(-1, cli_options.strict_port); // Let config system determine port, pass strict_port flag
|
||||||
@@ -1702,7 +1630,6 @@ int main(int argc, char* argv[]) {
|
|||||||
close_database();
|
close_database();
|
||||||
|
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
log_success("Server shutdown complete");
|
|
||||||
} else {
|
} else {
|
||||||
log_error("Server shutdown with errors");
|
log_error("Server shutdown with errors");
|
||||||
}
|
}
|
||||||
|
|||||||
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) {
|
if (store_event(event) != 0) {
|
||||||
log_warning("Failed to store deletion request event");
|
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
|
error_message[0] = '\0'; // Success - empty error message
|
||||||
return 0;
|
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) {
|
if (sqlite3_step(delete_stmt) == SQLITE_DONE && sqlite3_changes(g_db) > 0) {
|
||||||
deleted_count++;
|
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);
|
sqlite3_finalize(delete_stmt);
|
||||||
}
|
}
|
||||||
@@ -211,9 +203,6 @@ int delete_events_by_id(const char* requester_pubkey, cJSON* event_ids) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sqlite3_finalize(check_stmt);
|
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);
|
int changes = sqlite3_changes(g_db);
|
||||||
if (changes > 0) {
|
if (changes > 0) {
|
||||||
deleted_count += changes;
|
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);
|
sqlite3_finalize(delete_stmt);
|
||||||
|
|||||||
93
src/nip011.c
93
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
|
// Helper function to parse comma-separated string into cJSON array
|
||||||
cJSON* parse_comma_separated_array(const char* csv_string) {
|
cJSON* parse_comma_separated_array(const char* csv_string) {
|
||||||
log_info("parse_comma_separated_array called");
|
|
||||||
if (!csv_string || strlen(csv_string) == 0) {
|
if (!csv_string || strlen(csv_string) == 0) {
|
||||||
log_info("Empty or null csv_string, returning empty array");
|
|
||||||
return cJSON_CreateArray();
|
return cJSON_CreateArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
log_info("Creating cJSON array");
|
|
||||||
cJSON* array = cJSON_CreateArray();
|
cJSON* array = cJSON_CreateArray();
|
||||||
if (!array) {
|
if (!array) {
|
||||||
log_info("Failed to create cJSON array");
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
log_info("Duplicating csv_string");
|
|
||||||
char* csv_copy = strdup(csv_string);
|
char* csv_copy = strdup(csv_string);
|
||||||
if (!csv_copy) {
|
if (!csv_copy) {
|
||||||
log_info("Failed to duplicate csv_string");
|
|
||||||
cJSON_Delete(array);
|
cJSON_Delete(array);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
log_info("Starting token parsing");
|
|
||||||
char* token = strtok(csv_copy, ",");
|
char* token = strtok(csv_copy, ",");
|
||||||
while (token) {
|
while (token) {
|
||||||
log_info("Processing token");
|
|
||||||
// Trim whitespace
|
// Trim whitespace
|
||||||
while (*token == ' ') token++;
|
while (*token == ' ') token++;
|
||||||
char* end = token + strlen(token) - 1;
|
char* end = token + strlen(token) - 1;
|
||||||
while (end > token && *end == ' ') *end-- = '\0';
|
while (end > token && *end == ' ') *end-- = '\0';
|
||||||
|
|
||||||
if (strlen(token) > 0) {
|
if (strlen(token) > 0) {
|
||||||
log_info("Token has content, parsing");
|
|
||||||
// Try to parse as number first (for supported_nips)
|
// Try to parse as number first (for supported_nips)
|
||||||
char* endptr;
|
char* endptr;
|
||||||
long num = strtol(token, &endptr, 10);
|
long num = strtol(token, &endptr, 10);
|
||||||
if (*endptr == '\0') {
|
if (*endptr == '\0') {
|
||||||
log_info("Token is number, adding to array");
|
|
||||||
// It's a number
|
// It's a number
|
||||||
cJSON_AddItemToArray(array, cJSON_CreateNumber(num));
|
cJSON_AddItemToArray(array, cJSON_CreateNumber(num));
|
||||||
} else {
|
} else {
|
||||||
log_info("Token is string, adding to array");
|
|
||||||
// It's a string
|
// It's a string
|
||||||
cJSON_AddItemToArray(array, cJSON_CreateString(token));
|
cJSON_AddItemToArray(array, cJSON_CreateString(token));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log_info("Token is empty, skipping");
|
|
||||||
}
|
}
|
||||||
token = strtok(NULL, ",");
|
token = strtok(NULL, ",");
|
||||||
}
|
}
|
||||||
|
|
||||||
log_info("Freeing csv_copy");
|
|
||||||
free(csv_copy);
|
free(csv_copy);
|
||||||
log_info("Returning parsed array");
|
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize relay information using configuration system
|
// Initialize relay information using configuration system
|
||||||
void init_relay_info() {
|
void init_relay_info() {
|
||||||
log_info("Initializing relay information from configuration...");
|
|
||||||
|
|
||||||
// Get all config values first (without holding mutex to avoid deadlock)
|
// Get all config values first (without holding mutex to avoid deadlock)
|
||||||
// Note: These may be dynamically allocated strings that need to be freed
|
// 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");
|
const char* relay_name = get_config_value("relay_name");
|
||||||
log_info("relay_name fetched");
|
|
||||||
const char* relay_description = get_config_value("relay_description");
|
const char* relay_description = get_config_value("relay_description");
|
||||||
log_info("relay_description fetched");
|
|
||||||
const char* relay_software = get_config_value("relay_software");
|
const char* relay_software = get_config_value("relay_software");
|
||||||
log_info("relay_software fetched");
|
|
||||||
const char* relay_version = get_config_value("relay_version");
|
const char* relay_version = get_config_value("relay_version");
|
||||||
log_info("relay_version fetched");
|
|
||||||
const char* relay_contact = get_config_value("relay_contact");
|
const char* relay_contact = get_config_value("relay_contact");
|
||||||
log_info("relay_contact fetched");
|
|
||||||
const char* relay_pubkey = get_config_value("relay_pubkey");
|
const char* relay_pubkey = get_config_value("relay_pubkey");
|
||||||
log_info("relay_pubkey fetched");
|
|
||||||
const char* supported_nips_csv = get_config_value("supported_nips");
|
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");
|
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");
|
const char* relay_countries_csv = get_config_value("relay_countries");
|
||||||
log_info("relay_countries fetched");
|
|
||||||
const char* posting_policy = get_config_value("posting_policy");
|
const char* posting_policy = get_config_value("posting_policy");
|
||||||
log_info("posting_policy fetched");
|
|
||||||
const char* payments_url = get_config_value("payments_url");
|
const char* payments_url = get_config_value("payments_url");
|
||||||
log_info("payments_url fetched");
|
|
||||||
|
|
||||||
// Get config values for limitations
|
// Get config values for limitations
|
||||||
log_info("Fetching limitation configuration values...");
|
|
||||||
int max_message_length = get_config_int("max_message_length", 16384);
|
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);
|
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);
|
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);
|
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);
|
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);
|
int default_limit = get_config_int("default_limit", 500);
|
||||||
log_info("default_limit fetched");
|
|
||||||
int admin_enabled = get_config_bool("admin_enabled", 0);
|
int admin_enabled = get_config_bool("admin_enabled", 0);
|
||||||
log_info("admin_enabled fetched");
|
|
||||||
|
|
||||||
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
pthread_mutex_lock(&g_unified_cache.cache_lock);
|
||||||
|
|
||||||
// Update relay information fields
|
// Update relay information fields
|
||||||
log_info("Storing string values in cache...");
|
|
||||||
if (relay_name) {
|
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);
|
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
|
free((char*)relay_name); // Free dynamically allocated string
|
||||||
log_info("relay_name stored and freed");
|
|
||||||
} else {
|
} 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);
|
strncpy(g_unified_cache.relay_info.name, "C Nostr Relay", sizeof(g_unified_cache.relay_info.name) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relay_description) {
|
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);
|
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
|
free((char*)relay_description); // Free dynamically allocated string
|
||||||
log_info("relay_description stored and freed");
|
|
||||||
} else {
|
} 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);
|
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) {
|
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);
|
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
|
free((char*)relay_software); // Free dynamically allocated string
|
||||||
log_info("relay_software stored and freed");
|
|
||||||
} else {
|
} 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);
|
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) {
|
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);
|
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
|
free((char*)relay_version); // Free dynamically allocated string
|
||||||
log_info("relay_version stored and freed");
|
|
||||||
} else {
|
} 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);
|
strncpy(g_unified_cache.relay_info.version, "0.2.0", sizeof(g_unified_cache.relay_info.version) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relay_contact) {
|
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);
|
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
|
free((char*)relay_contact); // Free dynamically allocated string
|
||||||
log_info("relay_contact stored and freed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relay_pubkey) {
|
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);
|
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
|
free((char*)relay_pubkey); // Free dynamically allocated string
|
||||||
log_info("relay_pubkey stored and freed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (posting_policy) {
|
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);
|
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
|
free((char*)posting_policy); // Free dynamically allocated string
|
||||||
log_info("posting_policy stored and freed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payments_url) {
|
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);
|
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
|
free((char*)payments_url); // Free dynamically allocated string
|
||||||
log_info("payments_url stored and freed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize supported NIPs array from config
|
// Initialize supported NIPs array from config
|
||||||
log_info("Initializing supported_nips array");
|
|
||||||
if (supported_nips_csv) {
|
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);
|
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
|
free((char*)supported_nips_csv); // Free dynamically allocated string
|
||||||
log_info("supported_nips_csv freed");
|
|
||||||
} else {
|
} else {
|
||||||
log_info("Using default supported_nips");
|
|
||||||
// Fallback to default supported NIPs
|
// Fallback to default supported NIPs
|
||||||
g_unified_cache.relay_info.supported_nips = cJSON_CreateArray();
|
g_unified_cache.relay_info.supported_nips = cJSON_CreateArray();
|
||||||
if (g_unified_cache.relay_info.supported_nips) {
|
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(40)); // NIP-40: Expiration Timestamp
|
||||||
cJSON_AddItemToArray(g_unified_cache.relay_info.supported_nips, cJSON_CreateNumber(42)); // NIP-42: Authentication
|
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
|
// Initialize server limitations using configuration
|
||||||
log_info("Initializing server limitations");
|
|
||||||
g_unified_cache.relay_info.limitation = cJSON_CreateObject();
|
g_unified_cache.relay_info.limitation = cJSON_CreateObject();
|
||||||
if (g_unified_cache.relay_info.limitation) {
|
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_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_subscriptions", max_subscriptions_per_client);
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "max_limit", max_limit);
|
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_lower_limit", 0);
|
||||||
cJSON_AddNumberToObject(g_unified_cache.relay_info.limitation, "created_at_upper_limit", 2147483647);
|
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);
|
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)
|
// Initialize empty retention policies (can be configured later)
|
||||||
log_info("Initializing retention policies");
|
|
||||||
g_unified_cache.relay_info.retention = cJSON_CreateArray();
|
g_unified_cache.relay_info.retention = cJSON_CreateArray();
|
||||||
|
|
||||||
// Initialize language tags from config
|
// Initialize language tags from config
|
||||||
log_info("Initializing language_tags");
|
|
||||||
if (language_tags_csv) {
|
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);
|
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
|
free((char*)language_tags_csv); // Free dynamically allocated string
|
||||||
log_info("language_tags_csv freed");
|
|
||||||
} else {
|
} else {
|
||||||
log_info("Using default language_tags");
|
|
||||||
// Fallback to global
|
// Fallback to global
|
||||||
g_unified_cache.relay_info.language_tags = cJSON_CreateArray();
|
g_unified_cache.relay_info.language_tags = cJSON_CreateArray();
|
||||||
if (g_unified_cache.relay_info.language_tags) {
|
if (g_unified_cache.relay_info.language_tags) {
|
||||||
@@ -281,15 +206,10 @@ void init_relay_info() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize relay countries from config
|
// Initialize relay countries from config
|
||||||
log_info("Initializing relay_countries");
|
|
||||||
if (relay_countries_csv) {
|
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);
|
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
|
free((char*)relay_countries_csv); // Free dynamically allocated string
|
||||||
log_info("relay_countries_csv freed");
|
|
||||||
} else {
|
} else {
|
||||||
log_info("Using default relay_countries");
|
|
||||||
// Fallback to global
|
// Fallback to global
|
||||||
g_unified_cache.relay_info.relay_countries = cJSON_CreateArray();
|
g_unified_cache.relay_info.relay_countries = cJSON_CreateArray();
|
||||||
if (g_unified_cache.relay_info.relay_countries) {
|
if (g_unified_cache.relay_info.relay_countries) {
|
||||||
@@ -298,17 +218,12 @@ void init_relay_info() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize content tags as empty array
|
// Initialize content tags as empty array
|
||||||
log_info("Initializing tags");
|
|
||||||
g_unified_cache.relay_info.tags = cJSON_CreateArray();
|
g_unified_cache.relay_info.tags = cJSON_CreateArray();
|
||||||
|
|
||||||
// Initialize fees as empty object (no payment required by default)
|
// Initialize fees as empty object (no payment required by default)
|
||||||
log_info("Initializing fees");
|
|
||||||
g_unified_cache.relay_info.fees = cJSON_CreateObject();
|
g_unified_cache.relay_info.fees = cJSON_CreateObject();
|
||||||
|
|
||||||
log_info("Unlocking cache mutex");
|
|
||||||
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
|
||||||
log_success("Relay information initialized with default values");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up relay information JSON objects
|
// Clean up relay information JSON objects
|
||||||
@@ -518,7 +433,6 @@ cJSON* generate_relay_info_json() {
|
|||||||
g_unified_cache.relay_info.tags = cJSON_CreateArray();
|
g_unified_cache.relay_info.tags = cJSON_CreateArray();
|
||||||
g_unified_cache.relay_info.fees = cJSON_CreateObject();
|
g_unified_cache.relay_info.fees = cJSON_CreateObject();
|
||||||
|
|
||||||
log_info("NIP-11 relay_info rebuilt directly from config table");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add basic relay information
|
// Add basic relay information
|
||||||
@@ -610,7 +524,6 @@ struct nip11_session_data {
|
|||||||
|
|
||||||
// Handle NIP-11 HTTP request with proper asynchronous buffer management
|
// Handle NIP-11 HTTP request with proper asynchronous buffer management
|
||||||
int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
|
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
|
// Check if client accepts application/nostr+json
|
||||||
int accepts_nostr_json = 0;
|
int accepts_nostr_json = 0;
|
||||||
@@ -696,9 +609,6 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t json_len = strlen(json_string);
|
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
|
// Allocate session data to manage buffer lifetime across callbacks
|
||||||
struct nip11_session_data* session_data = malloc(sizeof(struct nip11_session_data));
|
struct nip11_session_data* session_data = malloc(sizeof(struct nip11_session_data));
|
||||||
@@ -791,8 +701,7 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
|
|||||||
|
|
||||||
// Request callback for body transmission
|
// Request callback for body transmission
|
||||||
lws_callback_on_writable(wsi);
|
lws_callback_on_writable(wsi);
|
||||||
|
|
||||||
log_success("NIP-11 headers sent, body transmission scheduled");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
29
src/nip013.c
29
src/nip013.c
@@ -27,7 +27,6 @@ struct pow_config {
|
|||||||
|
|
||||||
// Initialize PoW configuration using configuration system
|
// Initialize PoW configuration using configuration system
|
||||||
void init_pow_config() {
|
void init_pow_config() {
|
||||||
log_info("Initializing NIP-13 Proof of Work configuration");
|
|
||||||
|
|
||||||
// Get all config values first (without holding mutex to avoid deadlock)
|
// Get all config values first (without holding mutex to avoid deadlock)
|
||||||
int pow_enabled = get_config_bool("pow_enabled", 1);
|
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.reject_lower_targets = 1;
|
||||||
g_unified_cache.pow_config.strict_format = 1;
|
g_unified_cache.pow_config.strict_format = 1;
|
||||||
g_unified_cache.pow_config.anti_spam_mode = 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) {
|
} else if (strcmp(pow_mode, "full") == 0) {
|
||||||
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL;
|
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL;
|
||||||
g_unified_cache.pow_config.require_nonce_tag = 1;
|
g_unified_cache.pow_config.require_nonce_tag = 1;
|
||||||
log_info("PoW configured in full validation mode");
|
|
||||||
} else if (strcmp(pow_mode, "basic") == 0) {
|
} else if (strcmp(pow_mode, "basic") == 0) {
|
||||||
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
|
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) {
|
} else if (strcmp(pow_mode, "disabled") == 0) {
|
||||||
g_unified_cache.pow_config.enabled = 0;
|
g_unified_cache.pow_config.enabled = 0;
|
||||||
log_info("PoW validation disabled via configuration");
|
|
||||||
}
|
}
|
||||||
free((char*)pow_mode); // Free dynamically allocated string
|
free((char*)pow_mode); // Free dynamically allocated string
|
||||||
} else {
|
} else {
|
||||||
// Default to basic mode
|
// Default to basic mode
|
||||||
g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
|
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);
|
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;
|
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
|
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
|
// Initialize expiration configuration using configuration system
|
||||||
void init_expiration_config() {
|
void init_expiration_config() {
|
||||||
log_info("Initializing NIP-40 Expiration Timestamp configuration");
|
|
||||||
|
|
||||||
// Get all config values first (without holding mutex to avoid deadlock)
|
// Get all config values first (without holding mutex to avoid deadlock)
|
||||||
int expiration_enabled = get_config_bool("expiration_enabled", 1);
|
int expiration_enabled = get_config_bool("expiration_enabled", 1);
|
||||||
@@ -56,15 +55,6 @@ void init_expiration_config() {
|
|||||||
g_expiration_config.grace_period = 300;
|
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
|
// 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");
|
log_warning("Event rejected: expired timestamp");
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
// In non-strict mode, log but allow expired events
|
// In non-strict mode, allow expired events
|
||||||
char debug_msg[256];
|
|
||||||
snprintf(debug_msg, sizeof(debug_msg),
|
|
||||||
"Accepting expired event (strict_mode disabled)");
|
|
||||||
log_info(debug_msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,10 +83,6 @@ void send_nip42_auth_challenge(struct lws* wsi, struct per_session_data* pss) {
|
|||||||
free(msg_str);
|
free(msg_str);
|
||||||
}
|
}
|
||||||
cJSON_Delete(auth_msg);
|
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
|
// Handle NIP-42 signed authentication event from client
|
||||||
@@ -152,11 +148,6 @@ void handle_nip42_auth_signed_event(struct lws* wsi, struct per_session_data* ps
|
|||||||
pss->auth_challenge_sent = 0;
|
pss->auth_challenge_sent = 0;
|
||||||
pthread_mutex_unlock(&pss->session_lock);
|
pthread_mutex_unlock(&pss->session_lock);
|
||||||
|
|
||||||
char success_msg[256];
|
|
||||||
snprintf(success_msg, sizeof(success_msg),
|
|
||||||
"NIP-42 authentication successful for pubkey: %.16s...", authenticated_pubkey);
|
|
||||||
log_success(success_msg);
|
|
||||||
|
|
||||||
send_notice_message(wsi, "NIP-42 authentication successful");
|
send_notice_message(wsi, "NIP-42 authentication successful");
|
||||||
} else {
|
} else {
|
||||||
// Authentication failed
|
// Authentication failed
|
||||||
|
|||||||
@@ -139,21 +139,6 @@ struct {
|
|||||||
char reason[500]; // specific reason string
|
char reason[500]; // specific reason string
|
||||||
} g_last_rule_violation = {0};
|
} 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
|
// 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
|
// Initialize nostr_core_lib if not already done
|
||||||
if (nostr_crypto_init() != NOSTR_SUCCESS) {
|
if (nostr_crypto_init() != NOSTR_SUCCESS) {
|
||||||
validator_debug_log(
|
|
||||||
"VALIDATOR: Failed to initialize nostr crypto system\n");
|
|
||||||
return NOSTR_ERROR_CRYPTO_INIT;
|
return NOSTR_ERROR_CRYPTO_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load initial configuration from database
|
// Load initial configuration from database
|
||||||
int result = reload_auth_config();
|
int result = reload_auth_config();
|
||||||
if (result != NOSTR_SUCCESS) {
|
if (result != NOSTR_SUCCESS) {
|
||||||
validator_debug_log(
|
|
||||||
"VALIDATOR: Failed to load configuration from database\n");
|
|
||||||
return result;
|
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_challenge_manager.last_cleanup = time(NULL);
|
||||||
|
|
||||||
g_validator_initialized = 1;
|
g_validator_initialized = 1;
|
||||||
validator_debug_log(
|
|
||||||
"VALIDATOR: Request validator initialized successfully\n");
|
|
||||||
return NOSTR_SUCCESS;
|
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
|
// 1. Null Pointer Checks - Reject malformed requests instantly
|
||||||
if (!json_string || json_length == 0) {
|
if (!json_string || json_length == 0) {
|
||||||
validator_debug_log("VALIDATOR_DEBUG: STEP 1 FAILED - Null input\n");
|
|
||||||
return NOSTR_ERROR_INVALID_INPUT;
|
return NOSTR_ERROR_INVALID_INPUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Initialization Check - Verify system is properly initialized
|
// 2. Initialization Check - Verify system is properly initialized
|
||||||
if (!g_validator_initialized) {
|
if (!g_validator_initialized) {
|
||||||
validator_debug_log("VALIDATOR_DEBUG: STEP 2 FAILED - Validator not initialized\n");
|
|
||||||
return NOSTR_ERROR_INVALID_INPUT;
|
return NOSTR_ERROR_INVALID_INPUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Parse JSON string to cJSON event object
|
// 3. Parse JSON string to cJSON event object
|
||||||
cJSON *event = cJSON_ParseWithLength(json_string, json_length);
|
cJSON *event = cJSON_ParseWithLength(json_string, json_length);
|
||||||
if (!event) {
|
if (!event) {
|
||||||
validator_debug_log("VALIDATOR_DEBUG: STEP 3 FAILED - Failed to parse JSON event\n");
|
|
||||||
return NOSTR_ERROR_INVALID_INPUT;
|
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) ||
|
!tags || !cJSON_IsArray(tags) ||
|
||||||
!content || !cJSON_IsString(content) ||
|
!content || !cJSON_IsString(content) ||
|
||||||
!sig || !cJSON_IsString(sig)) {
|
!sig || !cJSON_IsString(sig)) {
|
||||||
validator_debug_log("VALIDATOR_DEBUG: STEP 4 FAILED - Invalid event structure\n");
|
|
||||||
cJSON_Delete(event);
|
cJSON_Delete(event);
|
||||||
return NOSTR_ERROR_INVALID_INPUT;
|
return NOSTR_ERROR_INVALID_INPUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
int event_kind = (int)cJSON_GetNumberValue(kind);
|
int event_kind = (int)cJSON_GetNumberValue(kind);
|
||||||
|
|
||||||
// 5. Check configuration using unified cache
|
// 5. Check configuration using unified cache
|
||||||
int auth_required = nostr_auth_rules_enabled();
|
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
|
// 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
|
// 6. Nostr Event Structure Validation using nostr_core_lib
|
||||||
int validation_result = nostr_validate_event(event);
|
int validation_result = nostr_validate_event(event);
|
||||||
if (validation_result != NOSTR_SUCCESS) {
|
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);
|
cJSON_Delete(event);
|
||||||
return validation_result;
|
return validation_result;
|
||||||
}
|
}
|
||||||
validator_debug_log("VALIDATOR_DEBUG: STEP 6 PASSED - Event structure and signature valid\n");
|
|
||||||
|
|
||||||
// 7. Extract pubkey for rule evaluation
|
// 7. Extract pubkey for rule evaluation
|
||||||
const char *event_pubkey = cJSON_GetStringValue(pubkey);
|
const char *event_pubkey = cJSON_GetStringValue(pubkey);
|
||||||
if (!event_pubkey || strlen(event_pubkey) != 64) {
|
if (!event_pubkey || strlen(event_pubkey) != 64) {
|
||||||
validator_debug_log("VALIDATOR_DEBUG: STEP 7 FAILED - Invalid pubkey format\n");
|
|
||||||
cJSON_Delete(event);
|
cJSON_Delete(event);
|
||||||
return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
|
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
|
// PHASE 3: EVENT KIND SPECIFIC VALIDATION
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// 8. Handle NIP-42 authentication challenge events (kind 22242)
|
// 8. Handle NIP-42 authentication challenge events (kind 22242)
|
||||||
if (event_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
|
// Check NIP-42 mode using unified cache
|
||||||
const char* nip42_enabled = get_config_value("nip42_auth_enabled");
|
const char* nip42_enabled = get_config_value("nip42_auth_enabled");
|
||||||
if (nip42_enabled && strcmp(nip42_enabled, "false") == 0) {
|
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);
|
free((char*)nip42_enabled);
|
||||||
cJSON_Delete(event);
|
cJSON_Delete(event);
|
||||||
return NOSTR_ERROR_NIP42_DISABLED;
|
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
|
// TODO: Implement full NIP-42 challenge validation
|
||||||
// For now, accept all valid NIP-42 events
|
// 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);
|
cJSON_Delete(event);
|
||||||
return NOSTR_SUCCESS;
|
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
|
// 9. Check if authentication rules are enabled
|
||||||
if (!auth_required) {
|
if (!auth_required) {
|
||||||
validator_debug_log("VALIDATOR_DEBUG: STEP 9 - Authentication disabled, skipping database auth rules\n");
|
|
||||||
} else {
|
} else {
|
||||||
// 10. Check database authentication rules (only if auth enabled)
|
// 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
|
// Create operation string with event kind for more specific rule matching
|
||||||
char operation_str[64];
|
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
|
// If generic event check fails, try specific event kind check
|
||||||
rules_result = check_database_auth_rules(event_pubkey, operation_str, NULL);
|
rules_result = check_database_auth_rules(event_pubkey, operation_str, NULL);
|
||||||
if (rules_result != NOSTR_SUCCESS) {
|
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);
|
cJSON_Delete(event);
|
||||||
return rules_result;
|
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);
|
pthread_mutex_unlock(&g_unified_cache.cache_lock);
|
||||||
|
|
||||||
if (pow_enabled && pow_min_difficulty > 0) {
|
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;
|
nostr_pow_result_t pow_result;
|
||||||
int pow_validation_result = nostr_validate_pow(event, pow_min_difficulty,
|
int pow_validation_result = nostr_validate_pow(event, pow_min_difficulty,
|
||||||
pow_validation_flags, &pow_result);
|
pow_validation_flags, &pow_result);
|
||||||
|
|
||||||
if (pow_validation_result != NOSTR_SUCCESS) {
|
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);
|
cJSON_Delete(event);
|
||||||
return pow_validation_result;
|
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
|
// 12. NIP-40 Expiration validation
|
||||||
// Always check expiration tags if present (following NIP-40 specification)
|
// 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 *expiration_tag = NULL;
|
||||||
cJSON *tags_array = cJSON_GetObjectItem(event, "tags");
|
cJSON *tags_array = cJSON_GetObjectItem(event, "tags");
|
||||||
|
|
||||||
if (tags_array && cJSON_IsArray(tags_array)) {
|
if (tags_array && cJSON_IsArray(tags_array)) {
|
||||||
cJSON *tag = NULL;
|
cJSON *tag = NULL;
|
||||||
cJSON_ArrayForEach(tag, tags_array) {
|
cJSON_ArrayForEach(tag, tags_array) {
|
||||||
if (!cJSON_IsArray(tag)) continue;
|
if (!cJSON_IsArray(tag)) continue;
|
||||||
|
|
||||||
cJSON *tag_name = cJSON_GetArrayItem(tag, 0);
|
cJSON *tag_name = cJSON_GetArrayItem(tag, 0);
|
||||||
if (!tag_name || !cJSON_IsString(tag_name)) continue;
|
if (!tag_name || !cJSON_IsString(tag_name)) continue;
|
||||||
|
|
||||||
const char *tag_name_str = cJSON_GetStringValue(tag_name);
|
const char *tag_name_str = cJSON_GetStringValue(tag_name);
|
||||||
if (strcmp(tag_name_str, "expiration") == 0) {
|
if (strcmp(tag_name_str, "expiration") == 0) {
|
||||||
cJSON *tag_value = cJSON_GetArrayItem(tag, 1);
|
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) {
|
if (expiration_tag) {
|
||||||
const char *expiration_str = cJSON_GetStringValue(expiration_tag);
|
const char *expiration_str = cJSON_GetStringValue(expiration_tag);
|
||||||
|
|
||||||
// Validate that the expiration string contains only digits (and optional leading whitespace)
|
// Validate that the expiration string contains only digits (and optional leading whitespace)
|
||||||
const char* p = expiration_str;
|
const char* p = expiration_str;
|
||||||
|
|
||||||
// Skip leading whitespace
|
// Skip leading whitespace
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
while (*p == ' ' || *p == '\t') p++;
|
||||||
|
|
||||||
// Check if we have at least one digit
|
// Check if we have at least one digit
|
||||||
if (*p == '\0') {
|
if (*p == '\0') {
|
||||||
validator_debug_log("VALIDATOR_DEBUG: STEP 12 SKIPPED - Empty expiration tag value, ignoring\n");
|
|
||||||
} else {
|
} else {
|
||||||
// Validate that all remaining characters are digits
|
// Validate that all remaining characters are digits
|
||||||
const char* digit_start = p;
|
const char* digit_start = p;
|
||||||
while (*p >= '0' && *p <= '9') p++;
|
while (*p >= '0' && *p <= '9') p++;
|
||||||
|
|
||||||
// If we didn't consume the entire string or found no digits, it's malformed
|
// If we didn't consume the entire string or found no digits, it's malformed
|
||||||
if (*p != '\0' || p == digit_start) {
|
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 {
|
} else {
|
||||||
// Valid numeric string, parse and check expiration
|
// Valid numeric string, parse and check expiration
|
||||||
time_t expiration_time = (time_t)atol(expiration_str);
|
time_t expiration_time = (time_t)atol(expiration_str);
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
int grace_period = get_config_int("nip40_expiration_grace_period", 60);
|
int grace_period = get_config_int("nip40_expiration_grace_period", 60);
|
||||||
|
|
||||||
if (expiration_time > 0 && now > expiration_time + grace_period) {
|
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);
|
cJSON_Delete(event);
|
||||||
return NOSTR_ERROR_EVENT_EXPIRED;
|
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
|
// All validations passed
|
||||||
validator_debug_log("VALIDATOR_DEBUG: STEP 13 PASSED - All validations complete, event ACCEPTED\n");
|
|
||||||
cJSON_Delete(event);
|
cJSON_Delete(event);
|
||||||
return NOSTR_SUCCESS;
|
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) {
|
void nostr_request_validator_force_cache_refresh(void) {
|
||||||
// Use unified cache refresh from config.c
|
// Use unified cache refresh from config.c
|
||||||
force_config_cache_refresh();
|
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) {
|
static int reload_auth_config(void) {
|
||||||
// Configuration is now handled by the unified cache in config.c
|
// 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;
|
return NOSTR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,35 +512,23 @@ static int reload_auth_config(void) {
|
|||||||
* Check database authentication rules for the request
|
* Check database authentication rules for the request
|
||||||
* Implements the 6-step rule evaluation engine from AUTH_API.md
|
* 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) {
|
const char *resource_hash) {
|
||||||
sqlite3 *db = NULL;
|
sqlite3 *db = NULL;
|
||||||
sqlite3_stmt *stmt = NULL;
|
sqlite3_stmt *stmt = NULL;
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
if (!pubkey) {
|
if (!pubkey) {
|
||||||
validator_debug_log(
|
|
||||||
"VALIDATOR_DEBUG: RULES ENGINE - Missing pubkey for rule evaluation\n");
|
|
||||||
return NOSTR_ERROR_INVALID_INPUT;
|
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
|
// Open database using global database path
|
||||||
if (strlen(g_database_path) == 0) {
|
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
|
return NOSTR_SUCCESS; // Default allow on DB error
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = sqlite3_open_v2(g_database_path, &db, SQLITE_OPEN_READONLY, NULL);
|
rc = sqlite3_open_v2(g_database_path, &db, SQLITE_OPEN_READONLY, NULL);
|
||||||
if (rc != SQLITE_OK) {
|
if (rc != SQLITE_OK) {
|
||||||
validator_debug_log(
|
|
||||||
"VALIDATOR_DEBUG: RULES ENGINE - Failed to open database\n");
|
|
||||||
return NOSTR_SUCCESS; // Default allow on DB error
|
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) {
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
const char *action = (const char *)sqlite3_column_text(stmt, 1);
|
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
|
// Set specific violation details for status code mapping
|
||||||
strcpy(g_last_rule_violation.violation_type, "pubkey_blacklist");
|
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);
|
sqlite3_finalize(stmt);
|
||||||
}
|
}
|
||||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 1 PASSED - Pubkey "
|
|
||||||
"not blacklisted\n");
|
|
||||||
|
|
||||||
// Step 2: Check hash blacklist
|
// Step 2: Check hash blacklist
|
||||||
if (resource_hash) {
|
if (resource_hash) {
|
||||||
@@ -673,14 +566,6 @@ int check_database_auth_rules(const char *pubkey, const char *operation,
|
|||||||
|
|
||||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
const char *action = (const char *)sqlite3_column_text(stmt, 1);
|
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
|
// Set specific violation details for status code mapping
|
||||||
strcpy(g_last_rule_violation.violation_type, "hash_blacklist");
|
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);
|
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
|
// 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);
|
sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_STATIC);
|
||||||
|
|
||||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
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_finalize(stmt);
|
||||||
sqlite3_close(db);
|
sqlite3_close(db);
|
||||||
return NOSTR_SUCCESS; // Allow whitelisted pubkey
|
return NOSTR_SUCCESS; // Allow whitelisted pubkey
|
||||||
}
|
}
|
||||||
sqlite3_finalize(stmt);
|
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
|
// Step 4: Check if any whitelist rules exist - if yes, deny by default
|
||||||
const char *whitelist_exists_sql =
|
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) {
|
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
int whitelist_count = sqlite3_column_int(stmt, 0);
|
int whitelist_count = sqlite3_column_int(stmt, 0);
|
||||||
if (whitelist_count > 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
|
// Set specific violation details for status code mapping
|
||||||
strcpy(g_last_rule_violation.violation_type, "whitelist_violation");
|
strcpy(g_last_rule_violation.violation_type, "whitelist_violation");
|
||||||
strcpy(g_last_rule_violation.reason,
|
strcpy(g_last_rule_violation.reason,
|
||||||
@@ -750,12 +617,8 @@ int check_database_auth_rules(const char *pubkey, const char *operation,
|
|||||||
}
|
}
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
}
|
}
|
||||||
validator_debug_log("VALIDATOR_DEBUG: RULES ENGINE - STEP 4 PASSED - No "
|
|
||||||
"whitelist restrictions apply\n");
|
|
||||||
|
|
||||||
sqlite3_close(db);
|
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
|
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;
|
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->expires_at = now + g_challenge_manager.timeout_seconds;
|
||||||
entry->active = 1;
|
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;
|
return NOSTR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -209,12 +209,7 @@ int add_subscription_to_manager(subscription_t* sub) {
|
|||||||
|
|
||||||
// Log subscription creation to database
|
// Log subscription creation to database
|
||||||
log_subscription_created(sub);
|
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;
|
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 events sent counter before freeing
|
||||||
update_subscription_events_sent(sub_id, sub->events_sent);
|
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);
|
free_subscription(sub);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -274,34 +264,24 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
|||||||
if (filter->kinds && cJSON_IsArray(filter->kinds)) {
|
if (filter->kinds && cJSON_IsArray(filter->kinds)) {
|
||||||
cJSON* event_kind = cJSON_GetObjectItem(event, "kind");
|
cJSON* event_kind = cJSON_GetObjectItem(event, "kind");
|
||||||
if (!event_kind || !cJSON_IsNumber(event_kind)) {
|
if (!event_kind || !cJSON_IsNumber(event_kind)) {
|
||||||
log_info("DEBUG FILTER: kinds filter present but event has no valid kind");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int event_kind_val = (int)cJSON_GetNumberValue(event_kind);
|
int event_kind_val = (int)cJSON_GetNumberValue(event_kind);
|
||||||
int kind_match = 0;
|
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* kind_item = NULL;
|
||||||
cJSON_ArrayForEach(kind_item, filter->kinds) {
|
cJSON_ArrayForEach(kind_item, filter->kinds) {
|
||||||
if (cJSON_IsNumber(kind_item)) {
|
if (cJSON_IsNumber(kind_item)) {
|
||||||
int filter_kind = (int)cJSON_GetNumberValue(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) {
|
if (filter_kind == event_kind_val) {
|
||||||
kind_match = 1;
|
kind_match = 1;
|
||||||
log_info("DEBUG FILTER: Kind match found!");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!kind_match) {
|
if (!kind_match) {
|
||||||
log_info("DEBUG FILTER: No kind match - filter fails");
|
|
||||||
return 0;
|
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)) {
|
if (filter->tag_filters && cJSON_IsObject(filter->tag_filters)) {
|
||||||
cJSON* event_tags = cJSON_GetObjectItem(event, "tags");
|
cJSON* event_tags = cJSON_GetObjectItem(event, "tags");
|
||||||
if (!event_tags || !cJSON_IsArray(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
|
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
|
// Check each tag filter
|
||||||
cJSON* tag_filter = NULL;
|
cJSON* tag_filter = NULL;
|
||||||
cJSON_ArrayForEach(tag_filter, filter->tag_filters) {
|
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 '#'
|
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)) {
|
if (!cJSON_IsArray(tag_filter)) {
|
||||||
continue; // Tag filter must be an array
|
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_name_str = cJSON_GetStringValue(event_tag_name);
|
||||||
const char* event_tag_value_str = cJSON_GetStringValue(event_tag_value);
|
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
|
// Check if tag name matches
|
||||||
if (strcmp(event_tag_name_str, tag_name) == 0) {
|
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
|
// Check if any of the filter values match this tag value
|
||||||
cJSON* filter_value = NULL;
|
cJSON* filter_value = NULL;
|
||||||
cJSON_ArrayForEach(filter_value, tag_filter) {
|
cJSON_ArrayForEach(filter_value, tag_filter) {
|
||||||
if (cJSON_IsString(filter_value)) {
|
if (cJSON_IsString(filter_value)) {
|
||||||
const char* filter_value_str = cJSON_GetStringValue(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
|
// Support prefix matching for tag values
|
||||||
if (strncmp(event_tag_value_str, filter_value_str, strlen(filter_value_str)) == 0) {
|
if (strncmp(event_tag_value_str, filter_value_str, strlen(filter_value_str)) == 0) {
|
||||||
tag_match = 1;
|
tag_match = 1;
|
||||||
log_info("DEBUG FILTER: Tag value match found!");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,9 +427,6 @@ int event_matches_filter(cJSON* event, subscription_filter_t* filter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!tag_match) {
|
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
|
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)
|
// Check if an event matches any filter in a subscription (filters are OR'd together)
|
||||||
int event_matches_subscription(cJSON* event, subscription_t* subscription) {
|
int event_matches_subscription(cJSON* event, subscription_t* subscription) {
|
||||||
if (!event || !subscription || !subscription->filters) {
|
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;
|
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;
|
subscription_filter_t* filter = subscription->filters;
|
||||||
int filter_index = 0;
|
|
||||||
while (filter) {
|
while (filter) {
|
||||||
int filter_matches = event_matches_filter(event, filter);
|
if (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);
|
|
||||||
return 1; // Match found (OR logic)
|
return 1; // Match found (OR logic)
|
||||||
}
|
}
|
||||||
filter = filter->next;
|
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
|
return 0; // No filters matched
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,11 +467,6 @@ int broadcast_event_to_subscriptions(cJSON* event) {
|
|||||||
if (expiration_enabled && filter_responses) {
|
if (expiration_enabled && filter_responses) {
|
||||||
time_t current_time = time(NULL);
|
time_t current_time = time(NULL);
|
||||||
if (is_event_expired(event, current_time)) {
|
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
|
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);
|
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;
|
subscription_t* sub = g_subscription_manager.active_subscriptions;
|
||||||
while (sub) {
|
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)) {
|
if (sub->active && event_matches_subscription(event, sub)) {
|
||||||
// Create EVENT message for this subscription
|
// Create EVENT message for this subscription
|
||||||
cJSON* event_msg = cJSON_CreateArray();
|
cJSON* event_msg = cJSON_CreateArray();
|
||||||
@@ -606,15 +517,9 @@ int broadcast_event_to_subscriptions(cJSON* event) {
|
|||||||
|
|
||||||
// Update global statistics
|
// Update global statistics
|
||||||
g_subscription_manager.total_events_broadcast += broadcasts;
|
g_subscription_manager.total_events_broadcast += broadcasts;
|
||||||
|
|
||||||
pthread_mutex_unlock(&g_subscription_manager.subscriptions_lock);
|
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;
|
return broadcasts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
252
src/websockets.c
252
src/websockets.c
@@ -95,6 +95,8 @@ extern unified_config_cache_t g_unified_cache;
|
|||||||
// Forward declarations for global state
|
// Forward declarations for global state
|
||||||
extern sqlite3* g_db;
|
extern sqlite3* g_db;
|
||||||
extern int g_server_running;
|
extern int g_server_running;
|
||||||
|
extern volatile sig_atomic_t g_shutdown_flag;
|
||||||
|
extern int g_restart_requested;
|
||||||
extern struct lws_context *ws_context;
|
extern struct lws_context *ws_context;
|
||||||
|
|
||||||
// Global subscription manager
|
// Global subscription manager
|
||||||
@@ -118,7 +120,6 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
// Handle HTTP requests
|
// Handle HTTP requests
|
||||||
{
|
{
|
||||||
char *requested_uri = (char *)in;
|
char *requested_uri = (char *)in;
|
||||||
log_info("HTTP request received");
|
|
||||||
|
|
||||||
// Check if this is an OPTIONS request
|
// Check if this is an OPTIONS request
|
||||||
char method[16] = {0};
|
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
|
// Handle HTTP body transmission for NIP-11 or embedded files
|
||||||
{
|
{
|
||||||
void* user_data = lws_wsi_user(wsi);
|
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) {
|
if (user_data) {
|
||||||
int type = *(int*)user_data;
|
int type = *(int*)user_data;
|
||||||
if (type == 0) {
|
if (type == 0) {
|
||||||
// NIP-11
|
// NIP-11
|
||||||
struct nip11_session_data* session_data = (struct nip11_session_data*)user_data;
|
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) {
|
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)
|
// Allocate buffer for JSON body transmission (no LWS_PRE needed for body)
|
||||||
unsigned char *json_buf = malloc(session_data->json_length);
|
unsigned char *json_buf = malloc(session_data->json_length);
|
||||||
if (!json_buf) {
|
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);
|
lws_set_wsi_user(wsi, NULL);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
log_info("NIP-11: Buffer allocated successfully");
|
|
||||||
|
|
||||||
// Copy JSON data to buffer
|
// Copy JSON data to buffer
|
||||||
memcpy(json_buf, session_data->json_buffer, session_data->json_length);
|
memcpy(json_buf, session_data->json_buffer, session_data->json_length);
|
||||||
@@ -234,7 +226,6 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
free(session_data);
|
free(session_data);
|
||||||
lws_set_wsi_user(wsi, NULL);
|
lws_set_wsi_user(wsi, NULL);
|
||||||
|
|
||||||
log_success("NIP-11 relay information served successfully");
|
|
||||||
return 0; // Close connection after successful transmission
|
return 0; // Close connection after successful transmission
|
||||||
}
|
}
|
||||||
} else if (type == 1) {
|
} else if (type == 1) {
|
||||||
@@ -246,7 +237,6 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case LWS_CALLBACK_ESTABLISHED:
|
case LWS_CALLBACK_ESTABLISHED:
|
||||||
log_info("WebSocket connection established");
|
|
||||||
memset(pss, 0, sizeof(*pss));
|
memset(pss, 0, sizeof(*pss));
|
||||||
pthread_mutex_init(&pss->session_lock, NULL);
|
pthread_mutex_init(&pss->session_lock, NULL);
|
||||||
|
|
||||||
@@ -278,24 +268,15 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
if (message) {
|
if (message) {
|
||||||
memcpy(message, in, len);
|
memcpy(message, in, len);
|
||||||
message[len] = '\0';
|
message[len] = '\0';
|
||||||
|
|
||||||
// Parse JSON message (this is the normal program flow)
|
// Parse JSON message (this is the normal program flow)
|
||||||
cJSON* json = cJSON_Parse(message);
|
cJSON* json = cJSON_Parse(message);
|
||||||
if (json && cJSON_IsArray(json)) {
|
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
|
// Get message type
|
||||||
cJSON* type = cJSON_GetArrayItem(json, 0);
|
cJSON* type = cJSON_GetArrayItem(json, 0);
|
||||||
if (type && cJSON_IsString(type)) {
|
if (type && cJSON_IsString(type)) {
|
||||||
const char* msg_type = cJSON_GetStringValue(type);
|
const char* msg_type = cJSON_GetStringValue(type);
|
||||||
|
|
||||||
if (strcmp(msg_type, "EVENT") == 0) {
|
if (strcmp(msg_type, "EVENT") == 0) {
|
||||||
// Extract event for kind-specific NIP-42 authentication check
|
// Extract event for kind-specific NIP-42 authentication check
|
||||||
cJSON* event_obj = cJSON_GetArrayItem(json, 1);
|
cJSON* event_obj = cJSON_GetArrayItem(json, 1);
|
||||||
@@ -303,19 +284,13 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
// Extract event kind for kind-specific NIP-42 authentication check
|
// Extract event kind for kind-specific NIP-42 authentication check
|
||||||
cJSON* kind_obj = cJSON_GetObjectItem(event_obj, "kind");
|
cJSON* kind_obj = cJSON_GetObjectItem(event_obj, "kind");
|
||||||
int event_kind = kind_obj && cJSON_IsNumber(kind_obj) ? (int)cJSON_GetNumberValue(kind_obj) : -1;
|
int event_kind = kind_obj && cJSON_IsNumber(kind_obj) ? (int)cJSON_GetNumberValue(kind_obj) : -1;
|
||||||
|
|
||||||
// Extract pubkey and event ID for debugging
|
// Extract pubkey and event ID for debugging
|
||||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event_obj, "pubkey");
|
cJSON* pubkey_obj = cJSON_GetObjectItem(event_obj, "pubkey");
|
||||||
cJSON* id_obj = cJSON_GetObjectItem(event_obj, "id");
|
cJSON* id_obj = cJSON_GetObjectItem(event_obj, "id");
|
||||||
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : "unknown";
|
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : "unknown";
|
||||||
const char* event_id = id_obj ? cJSON_GetStringValue(id_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
|
// 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);
|
int auth_required = is_nip42_auth_globally_required() || is_nip42_auth_required_for_kind(event_kind);
|
||||||
|
|
||||||
@@ -344,15 +319,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_required && !pss->authenticated && !bypass_auth) {
|
||||||
if (!pss->auth_challenge_sent) {
|
if (!pss->auth_challenge_sent) {
|
||||||
log_info("DEBUG AUTH: Sending NIP-42 authentication challenge");
|
|
||||||
send_nip42_auth_challenge(wsi, pss);
|
send_nip42_auth_challenge(wsi, pss);
|
||||||
} else {
|
} else {
|
||||||
char auth_msg[256];
|
char auth_msg[256];
|
||||||
@@ -365,9 +333,6 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
}
|
}
|
||||||
send_notice_message(wsi, auth_msg);
|
send_notice_message(wsi, auth_msg);
|
||||||
log_warning("Event rejected: NIP-42 authentication required for kind");
|
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);
|
cJSON_Delete(json);
|
||||||
free(message);
|
free(message);
|
||||||
@@ -448,20 +413,13 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
log_info("DEBUG VALIDATION: Starting unified validator");
|
|
||||||
|
|
||||||
// Call unified validator with JSON string
|
// Call unified validator with JSON string
|
||||||
size_t event_json_len = strlen(event_json_str);
|
size_t event_json_len = strlen(event_json_str);
|
||||||
int validation_result = nostr_validate_unified_request(event_json_str, event_json_len);
|
int validation_result = nostr_validate_unified_request(event_json_str, event_json_len);
|
||||||
|
|
||||||
// Map validation result to old result format (0 = success, -1 = failure)
|
// Map validation result to old result format (0 = success, -1 = failure)
|
||||||
int result = (validation_result == NOSTR_SUCCESS) ? 0 : -1;
|
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
|
// Generate error message based on validation result
|
||||||
char error_message[512] = {0};
|
char error_message[512] = {0};
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
@@ -491,12 +449,6 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
strncpy(error_message, "error: validation failed", sizeof(error_message) - 1);
|
strncpy(error_message, "error: validation failed", sizeof(error_message) - 1);
|
||||||
break;
|
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
|
// Cleanup event JSON string
|
||||||
@@ -543,8 +495,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);
|
strncpy(error_message, "auth-required: protected event requires authentication", sizeof(error_message) - 1);
|
||||||
error_message[sizeof(error_message) - 1] = '\0';
|
error_message[sizeof(error_message) - 1] = '\0';
|
||||||
log_warning("Protected event rejected: authentication required");
|
log_warning("Protected event rejected: authentication required");
|
||||||
} else {
|
|
||||||
log_info("Protected event accepted: authenticated publisher");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -555,63 +505,34 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||||
if (kind_obj && cJSON_IsNumber(kind_obj)) {
|
if (kind_obj && cJSON_IsNumber(kind_obj)) {
|
||||||
int event_kind = (int)cJSON_GetNumberValue(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
|
// Log reception of Kind 23456 events
|
||||||
if (event_kind == 23456) {
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event_kind == 23456) {
|
if (event_kind == 23456) {
|
||||||
// Enhanced admin event security - check authorization first
|
// Enhanced admin event security - check authorization first
|
||||||
log_info("DEBUG ADMIN: Admin event detected, checking authorization");
|
|
||||||
|
|
||||||
char auth_error[512] = {0};
|
char auth_error[512] = {0};
|
||||||
int auth_result = is_authorized_admin_event(event, auth_error, sizeof(auth_error));
|
int auth_result = is_authorized_admin_event(event, auth_error, sizeof(auth_error));
|
||||||
|
|
||||||
if (auth_result != 0) {
|
if (auth_result != 0) {
|
||||||
// Authorization failed - log and reject
|
// Authorization failed - log and reject
|
||||||
log_warning("DEBUG ADMIN: Admin event authorization failed");
|
log_warning("Admin event authorization failed");
|
||||||
result = -1;
|
result = -1;
|
||||||
size_t error_len = strlen(auth_error);
|
size_t error_len = strlen(auth_error);
|
||||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||||
memcpy(error_message, auth_error, copy_len);
|
memcpy(error_message, auth_error, copy_len);
|
||||||
error_message[copy_len] = '\0';
|
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 {
|
} else {
|
||||||
// Authorization successful - process through admin API
|
// Authorization successful - process through admin API
|
||||||
log_info("DEBUG ADMIN: Admin event authorized, processing through admin API");
|
|
||||||
|
|
||||||
char admin_error[512] = {0};
|
char admin_error[512] = {0};
|
||||||
int admin_result = process_admin_event_in_config(event, admin_error, sizeof(admin_error), wsi);
|
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
|
// Log results for Kind 23456 events
|
||||||
if (event_kind == 23456) {
|
if (event_kind == 23456) {
|
||||||
if (admin_result == 0) {
|
if (admin_result != 0) {
|
||||||
char success_result_msg[256];
|
|
||||||
snprintf(success_result_msg, sizeof(success_result_msg),
|
|
||||||
"SUCCESS: Kind %d event processed successfully", event_kind);
|
|
||||||
log_success(success_result_msg);
|
|
||||||
} else {
|
|
||||||
char error_result_msg[512];
|
char error_result_msg[512];
|
||||||
snprintf(error_result_msg, sizeof(error_result_msg),
|
snprintf(error_result_msg, sizeof(error_result_msg),
|
||||||
"ERROR: Kind %d event processing failed: %s", event_kind, admin_error);
|
"ERROR: Kind %d event processing failed: %s", event_kind, admin_error);
|
||||||
@@ -620,132 +541,101 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (admin_result != 0) {
|
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;
|
result = -1;
|
||||||
size_t error_len = strlen(admin_error);
|
size_t error_len = strlen(admin_error);
|
||||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||||
memcpy(error_message, admin_error, copy_len);
|
memcpy(error_message, admin_error, copy_len);
|
||||||
error_message[copy_len] = '\0';
|
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 {
|
} else {
|
||||||
log_success("DEBUG ADMIN: Admin event processed successfully through admin API");
|
|
||||||
// Admin events are processed by the admin API, not broadcast to subscriptions
|
// Admin events are processed by the admin API, not broadcast to subscriptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (event_kind == 1059) {
|
} else if (event_kind == 1059) {
|
||||||
// Check for NIP-17 gift wrap admin messages
|
// Check for NIP-17 gift wrap admin messages
|
||||||
log_info("DEBUG NIP17: Detected kind 1059 gift wrap event");
|
|
||||||
|
|
||||||
char nip17_error[512] = {0};
|
char nip17_error[512] = {0};
|
||||||
cJSON* response_event = process_nip17_admin_message(event, nip17_error, sizeof(nip17_error), wsi);
|
cJSON* response_event = process_nip17_admin_message(event, nip17_error, sizeof(nip17_error), wsi);
|
||||||
|
|
||||||
if (!response_event) {
|
if (!response_event) {
|
||||||
log_error("DEBUG NIP17: NIP-17 admin message processing failed");
|
// Check if this is an error or if the command was already handled
|
||||||
result = -1;
|
if (strlen(nip17_error) > 0) {
|
||||||
size_t error_len = strlen(nip17_error);
|
// There was an actual error
|
||||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
log_error("NIP-17 admin message processing failed");
|
||||||
memcpy(error_message, nip17_error, copy_len);
|
result = -1;
|
||||||
error_message[copy_len] = '\0';
|
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];
|
} else {
|
||||||
snprintf(debug_nip17_error_msg, sizeof(debug_nip17_error_msg),
|
// No error message means the command was already handled (plain text commands)
|
||||||
"DEBUG NIP17 ERROR: %.400s", nip17_error);
|
// Store the original gift wrap event in database
|
||||||
log_error(debug_nip17_error_msg);
|
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 {
|
} else {
|
||||||
log_success("DEBUG NIP17: NIP-17 admin message processed successfully");
|
|
||||||
// Store the original gift wrap event in database (unlike kind 23456)
|
// Store the original gift wrap event in database (unlike kind 23456)
|
||||||
if (store_event(event) != 0) {
|
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;
|
result = -1;
|
||||||
strncpy(error_message, "error: failed to store gift wrap event", sizeof(error_message) - 1);
|
strncpy(error_message, "error: failed to store gift wrap event", sizeof(error_message) - 1);
|
||||||
cJSON_Delete(response_event);
|
cJSON_Delete(response_event);
|
||||||
} else {
|
} 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcast RESPONSE event to matching persistent subscriptions
|
// Broadcast RESPONSE event to matching persistent subscriptions
|
||||||
int broadcast_count = broadcast_event_to_subscriptions(response_event);
|
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);
|
|
||||||
|
|
||||||
// Clean up response event
|
// Clean up response event
|
||||||
cJSON_Delete(response_event);
|
cJSON_Delete(response_event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (event_kind == 14) {
|
} else if (event_kind == 14) {
|
||||||
// Check for DM stats commands addressed to relay
|
// Check for DM stats commands addressed to relay
|
||||||
log_info("DEBUG DM: Detected kind 14 DM event");
|
|
||||||
|
|
||||||
char dm_error[512] = {0};
|
char dm_error[512] = {0};
|
||||||
int dm_result = process_dm_stats_command(event, dm_error, sizeof(dm_error), wsi);
|
int dm_result = process_dm_stats_command(event, dm_error, sizeof(dm_error), wsi);
|
||||||
|
|
||||||
if (dm_result != 0) {
|
if (dm_result != 0) {
|
||||||
log_error("DEBUG DM: DM stats command processing failed");
|
log_error("DM stats command processing failed");
|
||||||
result = -1;
|
result = -1;
|
||||||
size_t error_len = strlen(dm_error);
|
size_t error_len = strlen(dm_error);
|
||||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||||
memcpy(error_message, dm_error, copy_len);
|
memcpy(error_message, dm_error, copy_len);
|
||||||
error_message[copy_len] = '\0';
|
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 {
|
} else {
|
||||||
log_success("DEBUG DM: DM stats command processed successfully");
|
|
||||||
// Store the DM event in database
|
// Store the DM event in database
|
||||||
if (store_event(event) != 0) {
|
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;
|
result = -1;
|
||||||
strncpy(error_message, "error: failed to store DM event", sizeof(error_message) - 1);
|
strncpy(error_message, "error: failed to store DM event", sizeof(error_message) - 1);
|
||||||
} else {
|
} else {
|
||||||
log_info("DEBUG DM: DM event stored successfully in database");
|
|
||||||
// Broadcast DM event to matching persistent subscriptions
|
// Broadcast DM event to matching persistent subscriptions
|
||||||
int broadcast_count = broadcast_event_to_subscriptions(event);
|
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Regular event - store in database and broadcast
|
// Regular event - store in database and broadcast
|
||||||
log_info("DEBUG STORAGE: Regular event - storing in database");
|
|
||||||
if (store_event(event) != 0) {
|
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;
|
result = -1;
|
||||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||||
} else {
|
} else {
|
||||||
log_info("DEBUG STORAGE: Event stored successfully in database");
|
|
||||||
// Broadcast event to matching persistent subscriptions
|
// Broadcast event to matching persistent subscriptions
|
||||||
int broadcast_count = broadcast_event_to_subscriptions(event);
|
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Event without valid kind - try normal storage
|
// 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) {
|
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;
|
result = -1;
|
||||||
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
strncpy(error_message, "error: failed to store event", sizeof(error_message) - 1);
|
||||||
} else {
|
} else {
|
||||||
log_info("DEBUG STORAGE: Event without kind stored successfully in database");
|
|
||||||
broadcast_event_to_subscriptions(event);
|
broadcast_event_to_subscriptions(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -760,25 +650,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_CreateBool(result == 0));
|
||||||
cJSON_AddItemToArray(response, cJSON_CreateString(strlen(error_message) > 0 ? error_message : ""));
|
cJSON_AddItemToArray(response, cJSON_CreateString(strlen(error_message) > 0 ? error_message : ""));
|
||||||
|
|
||||||
// TODO: REPLACE - Remove wasteful cJSON_Print conversion
|
|
||||||
char *response_str = cJSON_Print(response);
|
char *response_str = cJSON_Print(response);
|
||||||
if (response_str) {
|
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);
|
size_t response_len = strlen(response_str);
|
||||||
unsigned char *buf = malloc(LWS_PRE + response_len);
|
unsigned char *buf = malloc(LWS_PRE + response_len);
|
||||||
if (buf) {
|
if (buf) {
|
||||||
memcpy(buf + LWS_PRE, response_str, response_len);
|
memcpy(buf + LWS_PRE, response_str, response_len);
|
||||||
int write_result = lws_write(wsi, buf + LWS_PRE, response_len, LWS_WRITE_TEXT);
|
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);
|
|
||||||
|
|
||||||
free(buf);
|
free(buf);
|
||||||
}
|
}
|
||||||
free(response_str);
|
free(response_str);
|
||||||
@@ -901,9 +779,7 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
pthread_mutex_unlock(&pss->session_lock);
|
pthread_mutex_unlock(&pss->session_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
char debug_msg[256];
|
// Subscription closed
|
||||||
snprintf(debug_msg, sizeof(debug_msg), "Closed subscription: %s", subscription_id);
|
|
||||||
log_info(debug_msg);
|
|
||||||
}
|
}
|
||||||
} else if (strcmp(msg_type, "AUTH") == 0) {
|
} else if (strcmp(msg_type, "AUTH") == 0) {
|
||||||
// Handle NIP-42 AUTH message
|
// Handle NIP-42 AUTH message
|
||||||
@@ -941,7 +817,6 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case LWS_CALLBACK_CLOSED:
|
case LWS_CALLBACK_CLOSED:
|
||||||
log_info("WebSocket connection closed");
|
|
||||||
|
|
||||||
// Clean up session subscriptions
|
// Clean up session subscriptions
|
||||||
if (pss) {
|
if (pss) {
|
||||||
@@ -1021,7 +896,7 @@ int check_port_available(int port) {
|
|||||||
int start_websocket_relay(int port_override, int strict_port) {
|
int start_websocket_relay(int port_override, int strict_port) {
|
||||||
struct lws_context_creation_info info;
|
struct lws_context_creation_info info;
|
||||||
|
|
||||||
log_info("Starting libwebsockets-based Nostr relay server...");
|
// Starting libwebsockets-based Nostr relay server
|
||||||
|
|
||||||
memset(&info, 0, sizeof(info));
|
memset(&info, 0, sizeof(info));
|
||||||
// Use port override if provided, otherwise use configuration
|
// Use port override if provided, otherwise use configuration
|
||||||
@@ -1049,9 +924,7 @@ int start_websocket_relay(int port_override, int strict_port) {
|
|||||||
|
|
||||||
// Find an available port with pre-checking (or fail immediately in strict mode)
|
// Find an available port with pre-checking (or fail immediately in strict mode)
|
||||||
while (port_attempts < (strict_port ? 1 : max_port_attempts)) {
|
while (port_attempts < (strict_port ? 1 : max_port_attempts)) {
|
||||||
char attempt_msg[256];
|
// Checking port availability
|
||||||
snprintf(attempt_msg, sizeof(attempt_msg), "Checking port availability: %d", actual_port);
|
|
||||||
log_info(attempt_msg);
|
|
||||||
|
|
||||||
// Pre-check if port is available
|
// Pre-check if port is available
|
||||||
if (!check_port_available(actual_port)) {
|
if (!check_port_available(actual_port)) {
|
||||||
@@ -1082,9 +955,7 @@ int start_websocket_relay(int port_override, int strict_port) {
|
|||||||
// Port appears available, try creating libwebsockets context
|
// Port appears available, try creating libwebsockets context
|
||||||
info.port = actual_port;
|
info.port = actual_port;
|
||||||
|
|
||||||
char binding_msg[256];
|
// Attempting to bind libwebsockets
|
||||||
snprintf(binding_msg, sizeof(binding_msg), "Attempting to bind libwebsockets to port %d", actual_port);
|
|
||||||
log_info(binding_msg);
|
|
||||||
|
|
||||||
ws_context = lws_create_context(&info);
|
ws_context = lws_create_context(&info);
|
||||||
if (ws_context) {
|
if (ws_context) {
|
||||||
@@ -1135,10 +1006,9 @@ int start_websocket_relay(int port_override, int strict_port) {
|
|||||||
} else {
|
} else {
|
||||||
snprintf(startup_msg, sizeof(startup_msg), "WebSocket relay started on ws://127.0.0.1:%d", actual_port);
|
snprintf(startup_msg, sizeof(startup_msg), "WebSocket relay started on ws://127.0.0.1:%d", actual_port);
|
||||||
}
|
}
|
||||||
log_success(startup_msg);
|
|
||||||
|
|
||||||
// Main event loop with proper signal handling
|
// 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);
|
int result = lws_service(ws_context, 1000);
|
||||||
|
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
@@ -1147,11 +1017,8 @@ int start_websocket_relay(int port_override, int strict_port) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log_info("Shutting down WebSocket server...");
|
|
||||||
lws_context_destroy(ws_context);
|
lws_context_destroy(ws_context);
|
||||||
ws_context = NULL;
|
ws_context = NULL;
|
||||||
|
|
||||||
log_success("WebSocket relay shut down cleanly");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1265,7 +1132,7 @@ int process_dm_stats_command(cJSON* dm_event, char* error_message, size_t error_
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
log_info("Processing DM stats command from admin");
|
// Processing DM stats command from admin
|
||||||
|
|
||||||
// Generate stats JSON
|
// Generate stats JSON
|
||||||
char* stats_json = generate_stats_json();
|
char* stats_json = generate_stats_json();
|
||||||
@@ -1316,15 +1183,10 @@ int process_dm_stats_command(cJSON* dm_event, char* error_message, size_t error_
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast to subscriptions
|
// Broadcast to subscriptions
|
||||||
int broadcast_count = broadcast_event_to_subscriptions(dm_response);
|
broadcast_event_to_subscriptions(dm_response);
|
||||||
char broadcast_msg[128];
|
|
||||||
snprintf(broadcast_msg, sizeof(broadcast_msg),
|
|
||||||
"DM stats response broadcast to %d subscriptions", broadcast_count);
|
|
||||||
log_info(broadcast_msg);
|
|
||||||
|
|
||||||
cJSON_Delete(dm_response);
|
cJSON_Delete(dm_response);
|
||||||
|
|
||||||
log_success("DM stats command processed successfully");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1332,7 +1194,6 @@ int process_dm_stats_command(cJSON* dm_event, char* error_message, size_t error_
|
|||||||
// Handle NIP-45 COUNT message
|
// Handle NIP-45 COUNT message
|
||||||
int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, struct per_session_data *pss) {
|
int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, struct per_session_data *pss) {
|
||||||
(void)pss; // Suppress unused parameter warning
|
(void)pss; // Suppress unused parameter warning
|
||||||
log_info("Handling COUNT message for subscription");
|
|
||||||
|
|
||||||
if (!cJSON_IsArray(filters)) {
|
if (!cJSON_IsArray(filters)) {
|
||||||
log_error("COUNT filters is not an array");
|
log_error("COUNT filters is not an array");
|
||||||
@@ -1522,10 +1383,7 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
|
|||||||
remaining = sizeof(sql) - strlen(sql);
|
remaining = sizeof(sql) - strlen(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug: Log the SQL query being executed
|
// Execute count query
|
||||||
char debug_msg[1280];
|
|
||||||
snprintf(debug_msg, sizeof(debug_msg), "Executing COUNT SQL: %s", sql);
|
|
||||||
log_info(debug_msg);
|
|
||||||
|
|
||||||
// Execute count query
|
// Execute count query
|
||||||
sqlite3_stmt* stmt;
|
sqlite3_stmt* stmt;
|
||||||
@@ -1542,17 +1400,13 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
|
|||||||
filter_count = sqlite3_column_int(stmt, 0);
|
filter_count = sqlite3_column_int(stmt, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
char count_debug[128];
|
// Filter count calculated
|
||||||
snprintf(count_debug, sizeof(count_debug), "Filter %d returned count: %d", i + 1, filter_count);
|
|
||||||
log_info(count_debug);
|
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
total_count += filter_count;
|
total_count += filter_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
char total_debug[128];
|
// Total count calculated
|
||||||
snprintf(total_debug, sizeof(total_debug), "Total COUNT result: %d", total_count);
|
|
||||||
log_info(total_debug);
|
|
||||||
|
|
||||||
// Send COUNT response - NIP-45 format: ["COUNT", <subscription_id>, {"count": <count>}]
|
// Send COUNT response - NIP-45 format: ["COUNT", <subscription_id>, {"count": <count>}]
|
||||||
cJSON* count_response = cJSON_CreateArray();
|
cJSON* count_response = cJSON_CreateArray();
|
||||||
|
|||||||
Reference in New Issue
Block a user