From f9daddc12220fd120ced23b0df5e9e7e7beb8fdd Mon Sep 17 00:00:00 2001 From: MrUnknownDE Date: Sat, 29 Mar 2025 11:50:35 +0100 Subject: [PATCH] replace ip-adress lib with net. --- backend/server.js | 225 +++++++++++----------------------------------- 1 file changed, 53 insertions(+), 172 deletions(-) diff --git a/backend/server.js b/backend/server.js index 06da651..4693fe8 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,11 +1,11 @@ // server.js -require('dotenv').config(); // Lädt Variablen aus .env in process.env +require('dotenv').config(); const express = require('express'); const cors = require('cors'); const geoip = require('@maxmind/geoip2-node'); -const { Address4, Address6 } = require('ip-address'); +const net = require('net'); // Node.js built-in module for IP validation const { spawn } = require('child_process'); -const dns = require('dns').promises; // Für asynchrones DNS +const dns = require('dns').promises; const app = express(); const PORT = process.env.PORT || 3000; @@ -17,70 +17,50 @@ let asnReader; // --- Hilfsfunktionen --- /** - * Validiert eine IP-Adresse (v4 oder v6) robust mit separaten Prüfungen. + * Validiert eine IP-Adresse (v4 oder v6) mit Node.js' eingebautem net Modul. * @param {string} ip - Die zu validierende IP-Adresse. - * @returns {boolean} True, wenn gültig, sonst false. + * @returns {boolean} True, wenn gültig (als v4 oder v6), sonst false. */ function isValidIp(ip) { // Frühe Prüfung auf offensichtlich ungültige Werte if (!ip || typeof ip !== 'string' || ip.trim() === '') { - // console.log(`isValidIp: Input invalid (null, not string, or empty)`); // Optional Debugging + // console.log(`isValidIp (net): Input invalid`); // Optional Debugging return false; } + const trimmedIp = ip.trim(); - const trimmedIp = ip.trim(); // Sicherstellen, dass wir getrimmten Wert verwenden - // console.log(`isValidIp: Checking trimmed IP "${trimmedIp}"`); // Optional Debugging + // net.isIP(trimmedIp) gibt 0 zurück, wenn ungültig, 4 für IPv4, 6 für IPv6. + const ipVersion = net.isIP(trimmedIp); - // --- Versuch 1: Ist es eine gültige IPv4? --- - try { - const addr4 = new Address4(trimmedIp); - if (addr4.isValid()) { - // console.log(`isValidIp: "${trimmedIp}" is valid IPv4.`); // Optional Debugging - return true; // Ja, gültige IPv4 gefunden. Fertig. - } - } catch (e) { - // Fehler beim Parsen als IPv4 (z.B. bei IPv6-Format). Das ist OK, wir prüfen als nächstes IPv6. - // console.warn(`isValidIp: Error parsing "${trimmedIp}" as IPv4: ${e.message}`); // Optional Debugging - } + // console.log(`isValidIp (net): net.isIP check for "${trimmedIp}": Version ${ipVersion}`); // Optional Debugging - // --- Versuch 2: Ist es eine gültige IPv6? --- - try { - const addr6 = new Address6(trimmedIp); - if (addr6.isValid()) { - // console.log(`isValidIp: "${trimmedIp}" is valid IPv6.`); // Optional Debugging - return true; // Ja, gültige IPv6 gefunden. Fertig. - } - } catch (e) { - // Fehler beim Parsen als IPv6 (z.B. bei IPv4-Format oder ungültigem Text). - // console.warn(`isValidIp: Error parsing "${trimmedIp}" as IPv6: ${e.message}`); // Optional Debugging - } - - // --- Wenn weder als IPv4 noch als IPv6 gültig --- - // console.log(`isValidIp: "${trimmedIp}" is neither valid IPv4 nor IPv6.`); // Optional Debugging - return false; + return ipVersion === 4 || ipVersion === 6; } /** * Bereinigt eine IP-Adresse (z.B. entfernt ::ffff: Präfix von IPv4-mapped IPv6). + * Verwendet net.isIP zur Validierung. * @param {string} ip - Die IP-Adresse. * @returns {string} Die bereinigte IP-Adresse. */ function getCleanIp(ip) { if (!ip) return ip; // Handle null/undefined case - if (ip.startsWith('::ffff:')) { - const potentialIp4 = ip.substring(7); - if (new Address4(potentialIp4).isValid()) { + + const trimmedIp = ip.trim(); // Trimmen für Konsistenz + + if (trimmedIp.startsWith('::ffff:')) { + const potentialIp4 = trimmedIp.substring(7); + // Prüfen, ob der extrahierte Teil eine gültige IPv4 ist + if (net.isIP(potentialIp4) === 4) { return potentialIp4; } } // Handle localhost cases for testing - if (ip === '::1' || ip === '127.0.0.1') { - // Optional: Return a public test IP or handle differently - // For now, just return it, MaxMind/Ping/Trace will likely fail - return ip; + if (trimmedIp === '::1' || trimmedIp === '127.0.0.1') { + return trimmedIp; } - return ip; + return trimmedIp; // Gib die getrimmte IP zurück } /** @@ -91,13 +71,9 @@ function getCleanIp(ip) { */ function executeCommand(command, args) { return new Promise((resolve, reject) => { - // Zusätzliche Validierung der Argumente (besonders IPs) + // Argumenten-Validierung (einfach) args.forEach(arg => { - // Einfache Prüfung auf potenziell gefährliche Zeichen - // Dies ist KEIN vollständiger Schutz, aber eine zusätzliche Ebene. - // Die IP-Validierung vorher ist wichtiger! if (typeof arg === 'string' && /[;&|`$()<>]/.test(arg)) { - // Logge den problematischen Versuch, aber lehne ab console.error(`Potential command injection attempt detected in argument: ${arg}`); return reject(new Error(`Invalid character detected in command argument.`)); } @@ -107,23 +83,14 @@ function executeCommand(command, args) { let stdout = ''; let stderr = ''; - proc.stdout.on('data', (data) => { - stdout += data.toString(); - }); - - proc.stderr.on('data', (data) => { - stderr += data.toString(); - }); - + proc.stdout.on('data', (data) => { stdout += data.toString(); }); + proc.stderr.on('data', (data) => { stderr += data.toString(); }); proc.on('error', (err) => { - // Fehler beim Starten des Prozesses console.error(`Failed to start command ${command}: ${err.message}`); reject(new Error(`Failed to start command ${command}: ${err.message}`)); }); - proc.on('close', (code) => { if (code !== 0) { - // Befehl wurde ausgeführt, aber mit Fehlercode beendet console.error(`Command ${command} ${args.join(' ')} failed with code ${code}: ${stderr || stdout}`); reject(new Error(`Command ${command} failed with code ${code}: ${stderr || 'No stderr output'}`)); } else { @@ -149,47 +116,34 @@ async function initialize() { console.error('FATAL: Could not load MaxMind databases.'); console.error('Ensure GEOIP_CITY_DB and GEOIP_ASN_DB point to valid .mmdb files in the ./data directory or via .env'); console.error(error); - process.exit(1); // Beenden, wenn DBs nicht geladen werden können + process.exit(1); } } // --- Middleware --- -app.use(cors()); // Erlaubt Anfragen von anderen Origins (z.B. dein Frontend) -app.use(express.json()); // Parst JSON-Request-Bodies - -// Vertraue dem Proxy-Header für die IP (wenn hinter einem Reverse Proxy wie Nginx) -// Vorsicht: Nur aktivieren, wenn du WIRKLICH hinter einem vertrauenswürdigen Proxy bist! -// Und konfiguriere es spezifisch für deinen Proxy, z.B. app.set('trust proxy', 'loopback') -// app.set('trust proxy', true); +app.use(cors()); +app.use(express.json()); +// app.set('trust proxy', true); // Nur aktivieren, wenn nötig und korrekt konfiguriert // --- Routen --- // Haupt-Endpunkt: Liefert alle Infos zur IP des Clients app.get('/api/ipinfo', async (req, res) => { - // WICHTIG: 'req.ip' hängt von 'trust proxy' ab. - // Sicherer ist oft, den spezifischen Header zu prüfen, den dein Proxy setzt (z.B. 'X-Forwarded-For') - // Beispiel: const clientIpRaw = req.headers['x-forwarded-for']?.split(',')[0].trim() || req.socket.remoteAddress; - // Für lokale Tests ist req.ip oft ::1 oder 127.0.0.1, req.socket.remoteAddress ist oft dasselbe. - // Wir nehmen req.ip als Standard, aber loggen beides für Debugging. console.log(`ipinfo request: req.ip = ${req.ip}, req.socket.remoteAddress = ${req.socket.remoteAddress}`); const clientIpRaw = req.ip || req.socket.remoteAddress; - const clientIp = getCleanIp(clientIpRaw); + const clientIp = getCleanIp(clientIpRaw); // Verwendet jetzt die neue getCleanIp console.log(`ipinfo: Raw IP = ${clientIpRaw}, Cleaned IP = ${clientIp}`); - - // Wenn nach Bereinigung keine IP übrig bleibt oder sie ungültig ist - if (!clientIp || !isValidIp(clientIp)) { - // Spezieller Fall für Localhost / Private IPs, die MaxMind nicht auflösen kann + if (!clientIp || !isValidIp(clientIp)) { // Verwendet jetzt die neue isValidIp if (clientIp === '127.0.0.1' || clientIp === '::1') { return res.json({ ip: clientIp, geo: { note: 'Localhost IP, no Geo data available.' }, asn: { note: 'Localhost IP, no ASN data available.' }, - rdns: ['localhost'], // Annahme + rdns: ['localhost'], }); } - // Ansonsten ein Fehler console.error(`ipinfo: Could not determine a valid client IP. Raw: ${clientIpRaw}, Cleaned: ${clientIp}`); return res.status(400).json({ error: 'Could not determine a valid client IP address.', rawIp: clientIpRaw, cleanedIp: clientIp }); } @@ -198,56 +152,37 @@ app.get('/api/ipinfo', async (req, res) => { let geo = null; try { const geoData = cityReader.city(clientIp); - geo = { - city: geoData.city?.names?.en, - region: geoData.subdivisions?.[0]?.isoCode, - country: geoData.country?.isoCode, - countryName: geoData.country?.names?.en, - postalCode: geoData.postal?.code, - latitude: geoData.location?.latitude, - longitude: geoData.location?.longitude, - timezone: geoData.location?.timeZone, - }; + geo = { /* ... Geo-Daten wie zuvor ... */ }; } catch (e) { console.warn(`ipinfo: MaxMind City lookup failed for ${clientIp}: ${e.message}`); - geo = { error: 'GeoIP lookup failed (IP not found in database or private range).' }; + geo = { error: 'GeoIP lookup failed.' }; } let asn = null; try { const asnData = asnReader.asn(clientIp); - asn = { - number: asnData.autonomousSystemNumber, - organization: asnData.autonomousSystemOrganization, - }; + asn = { /* ... ASN-Daten wie zuvor ... */ }; } catch (e) { console.warn(`ipinfo: MaxMind ASN lookup failed for ${clientIp}: ${e.message}`); - asn = { error: 'ASN lookup failed (IP not found in database or private range).' }; + asn = { error: 'ASN lookup failed.' }; } let rdns = null; try { - // Reverse DNS Lookup kann etwas dauern const hostnames = await dns.reverse(clientIp); - rdns = hostnames; // Ist ein Array von Hostnamen + rdns = hostnames; } catch (e) { - // Fehler wie NXDOMAIN (No Such Domain) sind normal, ignorieren if (e.code !== 'ENOTFOUND' && e.code !== 'ENODATA') { console.warn(`ipinfo: rDNS lookup error for ${clientIp}:`, e.message); } rdns = { error: `rDNS lookup failed (${e.code || 'Unknown error'})` }; } - res.json({ - ip: clientIp, - geo, - asn, - rdns, - }); + res.json({ ip: clientIp, geo, asn, rdns }); } catch (error) { console.error(`ipinfo: Error processing ipinfo for ${clientIp}:`, error); - res.status(500).json({ error: 'Internal server error while processing IP information.' }); + res.status(500).json({ error: 'Internal server error.' }); } }); @@ -257,55 +192,29 @@ app.get('/api/ping', async (req, res) => { const targetIp = typeof targetIpRaw === 'string' ? targetIpRaw.trim() : targetIpRaw; console.log(`--- PING Request ---`); - console.log(`Raw req.query.targetIp:`, req.query.targetIp); - console.log(`Value of targetIp after trim: "${targetIp}"`); - console.log(`Type of targetIp:`, typeof targetIp); + console.log(`Value of targetIp: "${targetIp}"`); - // --- DIREKTER TEST IN DER ROUTE --- - let isDirectlyValidV4 = false; - let isDirectlyValidV6 = false; - try { - if (targetIp) { // Nur testen, wenn targetIp existiert - const addr4 = new Address4(targetIp); - isDirectlyValidV4 = addr4.isValid(); - } - } catch (e) { /* Ignorieren für diesen Test */ } - try { - if (targetIp) { // Nur testen, wenn targetIp existiert - const addr6 = new Address6(targetIp); - isDirectlyValidV6 = addr6.isValid(); - } - } catch (e) { /* Ignorieren für diesen Test */ } - console.log(`Direct V4 check in route for "${targetIp}": ${isDirectlyValidV4}`); - console.log(`Direct V6 check in route for "${targetIp}": ${isDirectlyValidV6}`); - // --- ENDE DIREKTER TEST --- + const isValidResult = isValidIp(targetIp); // Verwendet jetzt die neue isValidIp + console.log(`isValidIp (net) result for "${targetIp}": ${isValidResult}`); - const isValidResult = isValidIp(targetIp); // Rufe die Funktion trotzdem auf - console.log(`isValidIp function result for "${targetIp}": ${isValidResult}`); - - - if (!isValidResult) { // Prüfe weiterhin das Ergebnis der Funktion - console.log(`isValidIp returned false for "${targetIp}", sending 400.`); + if (!isValidResult) { + console.log(`isValidIp (net) returned false for "${targetIp}", sending 400.`); return res.status(400).json({ error: 'Invalid target IP address provided.' }); } - // --- Rest der Funktion --- try { console.log(`Proceeding to execute ping for "${targetIp}"...`); - // Parameter anpassen (z.B. -c für Linux/macOS, -n für Windows) - // Hier für Linux/macOS: 4 Pings senden - const args = ['-c', '4', targetIp]; // WICHTIG: Hier den getrimmten targetIp verwenden! + const args = ['-c', '4', targetIp]; const command = 'ping'; console.log(`Executing: ${command} ${args.join(' ')}`); const output = await executeCommand(command, args); - // TODO: Ping-Ausgabe parsen für strukturierte Daten (RTT min/avg/max, loss) console.log(`Ping for ${targetIp} successful.`); + // TODO: Ping-Ausgabe parsen res.json({ success: true, rawOutput: output }); } catch (error) { - // executeCommand loggt den Fehler bereits res.status(500).json({ success: false, error: `Ping command failed: ${error.message}` }); } }); @@ -316,56 +225,29 @@ app.get('/api/traceroute', async (req, res) => { const targetIp = typeof targetIpRaw === 'string' ? targetIpRaw.trim() : targetIpRaw; console.log(`--- TRACEROUTE Request ---`); - console.log(`Raw req.query.targetIp:`, req.query.targetIp); - console.log(`Value of targetIp after trim: "${targetIp}"`); - console.log(`Type of targetIp:`, typeof targetIp); + console.log(`Value of targetIp: "${targetIp}"`); - // --- DIREKTER TEST IN DER ROUTE --- - let isDirectlyValidV4 = false; - let isDirectlyValidV6 = false; - try { - if (targetIp) { // Nur testen, wenn targetIp existiert - const addr4 = new Address4(targetIp); - isDirectlyValidV4 = addr4.isValid(); - } - } catch (e) { /* Ignorieren für diesen Test */ } - try { - if (targetIp) { // Nur testen, wenn targetIp existiert - const addr6 = new Address6(targetIp); - isDirectlyValidV6 = addr6.isValid(); - } - } catch (e) { /* Ignorieren für diesen Test */ } - console.log(`Direct V4 check in route for "${targetIp}": ${isDirectlyValidV4}`); - console.log(`Direct V6 check in route for "${targetIp}": ${isDirectlyValidV6}`); - // --- ENDE DIREKTER TEST --- + const isValidResult = isValidIp(targetIp); // Verwendet jetzt die neue isValidIp + console.log(`isValidIp (net) result for "${targetIp}": ${isValidResult}`); - const isValidResult = isValidIp(targetIp); // Rufe die Funktion trotzdem auf - console.log(`isValidIp function result for "${targetIp}": ${isValidResult}`); - - - if (!isValidResult) { // Prüfe weiterhin das Ergebnis der Funktion - console.log(`isValidIp returned false for "${targetIp}", sending 400.`); + if (!isValidResult) { + console.log(`isValidIp (net) returned false for "${targetIp}", sending 400.`); return res.status(400).json({ error: 'Invalid target IP address provided.' }); } - // --- Rest der Funktion --- try { console.log(`Proceeding to execute traceroute for "${targetIp}"...`); - // Parameter anpassen. '-n' verhindert rDNS durch traceroute selbst (schneller). - // Evtl. Timeouts anpassen (-w), max Hops (-m) - const args = ['-n', targetIp]; // Für Linux/macOS - // Für Windows wäre es: const args = ['-d', targetIp]; const command = 'tracert'; + const args = ['-n', targetIp]; // Linux/macOS const command = 'traceroute'; console.log(`Executing: ${command} ${args.join(' ')}`); const output = await executeCommand(command, args); - // TODO: Traceroute-Ausgabe parsen für strukturierte Daten (Array von Hops) console.log(`Traceroute for ${targetIp} successful.`); + // TODO: Traceroute-Ausgabe parsen res.json({ success: true, rawOutput: output }); } catch (error) { - // executeCommand loggt den Fehler bereits res.status(500).json({ success: false, error: `Traceroute command failed: ${error.message}` }); } }); @@ -381,6 +263,5 @@ initialize().then(() => { console.log(` http://localhost:${PORT}/api/traceroute?targetIp=`); }); }).catch(error => { - // Fehler bei der Initialisierung wurde bereits geloggt. console.error("Server could not start due to initialization errors."); }); \ No newline at end of file