diff --git a/package-lock.json b/package-lock.json
index 12c7186..03ea968 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "fm-dx-webserver",
- "version": "1.4.0",
+ "version": "1.4.0a",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fm-dx-webserver",
- "version": "1.4.0",
+ "version": "1.4.0a",
"license": "ISC",
"dependencies": {
"@mapbox/node-pre-gyp": "2.0.3",
@@ -17,7 +17,6 @@
"ffmpeg-static": "5.3.0",
"figlet": "^1.10.0",
"http": "0.0.1-security",
- "koffi": "2.7.2",
"net": "1.0.2",
"serialport": "13.0.0",
"ws": "8.19.0"
@@ -1168,13 +1167,6 @@
"node": ">=10"
}
},
- "node_modules/koffi": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.7.2.tgz",
- "integrity": "sha512-AWcsEKETQuELxK0Wq/aXDkDiNFFY41TxZQSrKm2Nd6HO/KTHeohPOOIlh2OfQnBXJbRjx5etpWt8cbqMUZo2sg==",
- "hasInstallScript": true,
- "license": "MIT"
- },
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
diff --git a/server/chat.js b/server/chat.js
index f46655f..2ae5b39 100644
--- a/server/chat.js
+++ b/server/chat.js
@@ -16,7 +16,7 @@ function createChatServer(storage) {
ws.isAlive = true;
ws.on('pong', heartbeat);
- const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
+ const clientIp = helpers.getIpAddress(request);
const userCommandHistory = {};
if (serverConfig.webserver.banlist?.includes(clientIp)) {
diff --git a/server/console.js b/server/console.js
index 0e17a08..dff1dce 100644
--- a/server/console.js
+++ b/server/console.js
@@ -21,6 +21,7 @@ const MESSAGE_PREFIX = {
FFMPEG: "\x1b[36m[FFMPEG]\x1b[0m",
INFO: "\x1b[32m[INFO]\x1b[0m",
WARN: "\x1b[33m[WARN]\x1b[0m",
+ SECURITY: "\x1b[36m[SECURITY]\x1b[0m",
};
const getCurrentTime = () => {
@@ -44,6 +45,7 @@ const logMessage = (type, messages) => {
if(type !== 'FFMPEG') appendLogToBuffer(logMessage);
};
+const logSecurity = (...messages) => logMessage('SECURITY', messages);
const logDebug = (...messages) => logMessage('DEBUG', messages);
const logChat = (message) => logMessage('CHAT', [`${message.nickname} (${message.ip}) sent a chat message: ${message.message}`]);
const logError = (...messages) => logMessage('ERROR', messages);
@@ -87,4 +89,4 @@ process.on('exit', flushLogBuffer);
process.on('SIGINT', gracefulExit);
process.on('SIGTERM', gracefulExit);
-module.exports = { logError, logDebug, logFfmpeg, logInfo, logWarn, logs, logChat };
\ No newline at end of file
+module.exports = { logError, logDebug, logFfmpeg, logInfo, logWarn, logs, logChat, logSecurity };
\ No newline at end of file
diff --git a/server/datahandler.js b/server/datahandler.js
index dfa03fd..bed09ad 100644
--- a/server/datahandler.js
+++ b/server/datahandler.js
@@ -106,8 +106,8 @@ function handleData(wss, receivedData, rdsWss) {
case receivedLine.startsWith('T'): // Frequency
modifiedData = receivedLine.substring(1).split(",")[0];
- rdsReset();
if((modifiedData / 1000).toFixed(3) == dataToSend.freq) return; // Prevent tune spamming using scrollwheel
+ rdsReset();
parsedValue = parseFloat(modifiedData);
diff --git a/server/endpoints.js b/server/endpoints.js
index 4748904..bb3fbfd 100644
--- a/server/endpoints.js
+++ b/server/endpoints.js
@@ -19,10 +19,10 @@ const allPluginConfigs = require('./plugins');
// Endpoints
router.get('/', (req, res) => {
- let requestIp = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
+ let requestIp = helpers.getIpAddress(req);
const normalizedIp = requestIp?.replace(/^::ffff:/, '');
- const ipList = (normalizedIp || '').split(',').map(ip => ip.trim()).filter(Boolean); // in case there are multiple IPs (proxy), we need to check all of them
+ const ipList = (normalizedIp || '').split(',').map(ip => ip.trim()).filter(Boolean); // in case there are multiple IPs (proxy), we need to check all of them (No we don't)
const banEntry = serverConfig.webserver.banlist.find(banEntry => ipList.includes(banEntry[0]));
@@ -385,7 +385,7 @@ router.get('/log_fmlist', (req, res) => {
return;
}
- const clientIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
+ const clientIp = helpers.getIpAddress(req);
const txId = dataHandler.dataToSend.txInfo.id; // Extract the ID
if (!canLog(txId)) {
diff --git a/server/helpers.js b/server/helpers.js
index f1bd5f4..caab540 100644
--- a/server/helpers.js
+++ b/server/helpers.js
@@ -343,10 +343,24 @@ function findServerFiles(plugins) {
return results;
}
+function getIpAddress(request) {
+ const remoteIp = request.socket.remoteAddress;
+ const xff = request.headers['x-forwarded-for'];
+
+ // If X-Forwarded-For is present but request is NOT from a trusted proxy → suspicious
+ if (xff && !serverConfig.trustedProxies.includes(remoteIp)) {
+ consoleCmd.logSecurity(`Untrusted proxy tried to set X-Forwarded-For: ${xff} (remote: ${remoteIp})`);
+ return remoteIp;
+ }
+
+ if (xff && serverConfig.trustedProxies.includes(remoteIp)) return xff.split(',')[0].trim();
+ return remoteIp;
+}
+
module.exports = {
authenticateWithXdrd, parseMarkdown, handleConnect,
removeMarkdown, formatUptime, resolveDataBuffer,
kickClient, checkIPv6Support, checkLatency,
antispamProtection, escapeHtml, findServerFiles,
- startPluginsWithDelay
+ startPluginsWithDelay, getIpAddress
}
\ No newline at end of file
diff --git a/server/index.js b/server/index.js
index aa667c4..fa847ca 100644
--- a/server/index.js
+++ b/server/index.js
@@ -301,7 +301,7 @@ setInterval(() => {
wss.on('connection', (ws, request) => {
const output = serverConfig.xdrd.wirelessConnection ? client : serialport;
- let clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
+ let clientIp = helpers.getIpAddress(request);
const userCommandHistory = {};
const normalizedClientIp = clientIp?.replace(/^::ffff:/, '');
@@ -486,7 +486,7 @@ wss.on('connection', (ws, request) => {
// Additional web socket for using plugins
pluginsWss.on('connection', (ws, request) => {
- const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
+ const clientIp = helpers.getIpAddress(request);
const userCommandHistory = {};
if (serverConfig.webserver.banlist?.includes(clientIp)) {
ws.close(1008, 'Banned IP');
@@ -524,7 +524,7 @@ pluginsWss.on('connection', (ws, request) => {
// Websocket register for /text, /audio and /chat paths
httpServer.on('upgrade', (request, socket, head) => {
- const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
+ const clientIp = helpers.getIpAddress(request);
if (serverConfig.webserver.banlist?.includes(clientIp)) {
socket.destroy();
return;
diff --git a/server/rds.js b/server/rds.js
index 2aa318e..c165ecc 100644
--- a/server/rds.js
+++ b/server/rds.js
@@ -232,12 +232,20 @@ class RDSDecoder {
if(d_error > 2) return; // Don't risk it
+
const idx = blockB & 0x3;
+
+ const last_err = this.ps_errors[idx * 2];
+ const cur_err = Math.ceil(d_error * (10/3));
+ const character_a = decode_charset(blockD >> 8);
+ const character_b = decode_charset(blockD & 0xFF);
- this.ps[idx * 2] = decode_charset(blockD >> 8);
- this.ps[idx * 2 + 1] = decode_charset(blockD & 0xFF);
- this.ps_errors[idx * 2] = Math.ceil(d_error * (10/3));
- this.ps_errors[idx * 2 + 1] = Math.ceil(d_error * (10/3));
+ if(last_err < cur_err && this.ps[idx * 2] == character_a && this.ps[idx * 2 + 1] == character_b) return;
+
+ this.ps[idx * 2] = character_a;
+ this.ps[idx * 2 + 1] = character_b;
+ this.ps_errors[idx * 2] = cur_err;
+ this.ps_errors[idx * 2 + 1] = cur_err;
this.data.ps = this.ps.join('');
this.data.ps_errors = this.ps_errors.join(',');
diff --git a/server/rds_country.js b/server/rds_country.js
index 915fd12..a93077c 100644
--- a/server/rds_country.js
+++ b/server/rds_country.js
@@ -105,7 +105,7 @@ var countries = [
"Turkey",
"Palestine",
"Turkmenistan",
- "Poland",
+ "Rzeczpospolita Polska",
"Uganda",
"Portugal",
"Ukraine",
@@ -539,7 +539,7 @@ const rdsEccE0E5Lut = [
],
// E2
[
- "Morocco", "Czechia", "Poland", "Vatican", "Slovakia",
+ "Morocco", "Czechia", "Rzeczpospolita Polska", "Vatican", "Slovakia",
"Syria", "Tunisia", "", "Liechtenstein", "Iceland",
"Monaco", "Lithuania", "Serbia", "Spain", "Norway"
],
diff --git a/server/server_config.js b/server/server_config.js
index e1a065a..40ab241 100644
--- a/server/server_config.js
+++ b/server/server_config.js
@@ -126,7 +126,8 @@ let serverConfig = {
stereoNoUsers: "0",
antennaStartup: "0",
antennaNoUsers: "0",
- antennaNoUsersDelay: false
+ antennaNoUsersDelay: false,
+ trustedProxies: []
};
// Function to add missing fields without overwriting existing values
diff --git a/server/stream/ws.js b/server/stream/ws.js
index 12ec920..d68a156 100644
--- a/server/stream/ws.js
+++ b/server/stream/ws.js
@@ -1,11 +1,12 @@
const WebSocket = require('ws');
const { serverConfig } = require('../server_config');
const audio_pipe = require('./index.js');
+const { getIpAddress } = require("../helpers.js")
const audioWss = new WebSocket.Server({ noServer: true, skipUTF8Validation: true });
audioWss.on('connection', (ws, request) => {
- const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
+ const clientIp = getIpAddress(request);
if (serverConfig.webserver.banlist?.includes(clientIp)) {
ws.close(1008, 'Banned IP');
diff --git a/server/tx_search.js b/server/tx_search.js
index 7a8bdfe..71214de 100644
--- a/server/tx_search.js
+++ b/server/tx_search.js
@@ -26,7 +26,6 @@ const WebSocket = require('ws');
// Get weighting values based on algorithm setting.
// Defaults = algorithm 1
let weightedErp = 10;
-let weightedDist = 400;
const algorithms = [
[10, 400],
[30, 500],
@@ -268,7 +267,11 @@ async function fetchTx(freq, piCode, rdsPs) {
if (filteredLocations.length > 1) {
const extraFilteredLocations = filteredLocations.map(locData => ({
...locData,
- stations: locData.stations.filter(station => (station.ps?.toLowerCase() === rdsPs.replace(/ /g, '_').toLowerCase()))
+ stations: locData.stations.filter(station =>
+ station.ps?.toLowerCase().includes(
+ rdsPs.replace(/ /g, '_').toLowerCase()
+ ) ?? false
+ )
})).filter(locData => locData.stations.length > 0);
if (extraFilteredLocations.length > 0) filteredLocations = extraFilteredLocations;
@@ -370,8 +373,7 @@ function haversine(lat1, lon1, lat2, lon2) {
const y = Math.sin(dLon) * Math.cos(deg2rad(lat2));
const x = Math.cos(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) - Math.sin(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(dLon);
- const azimuth = Math.atan2(y, x);
- const azimuthDegrees = (azimuth * 180 / Math.PI + 360) % 360;
+ const azimuthDegrees = (Math.atan2(y, x) * 180 / Math.PI + 360) % 360;
return {
distanceKm: distance,
diff --git a/web/403.ejs b/web/403.ejs
index 2e388b7..819ec0d 100644
--- a/web/403.ejs
+++ b/web/403.ejs
@@ -21,7 +21,7 @@
- There's a possibility you were kicked by the system.
+ The service has rejected you.
Please try again later.