From 147fc168f9b624be2a36ec34da450eaffeb74353 Mon Sep 17 00:00:00 2001 From: decolua Date: Wed, 25 Feb 2026 10:40:15 +0700 Subject: [PATCH] fix bug --- package.json | 2 +- src/mitm/cert/install.js | 45 ++++++++++++++++++++++++++++++++----- src/mitm/dns/dnsConfig.js | 31 +++++++++++++++++++++----- src/mitm/manager.js | 47 +++++++++++++++++++++------------------ 4 files changed, 90 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index 5f7457d..295534f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "9router-app", - "version": "0.2.95", + "version": "0.2.98", "description": "9Router web dashboard", "private": true, "scripts": { diff --git a/src/mitm/cert/install.js b/src/mitm/cert/install.js index a0d29d4..dfe9bc2 100644 --- a/src/mitm/cert/install.js +++ b/src/mitm/cert/install.js @@ -4,6 +4,8 @@ const { exec } = require("child_process"); const { execWithPassword } = require("../dns/dnsConfig.js"); const IS_WIN = process.platform === "win32"; +const IS_MAC = process.platform === "darwin"; +const LINUX_CERT_DIR = "/usr/local/share/ca-certificates"; // Get SHA1 fingerprint from cert file using Node.js crypto function getCertFingerprint(certPath) { @@ -16,10 +18,9 @@ function getCertFingerprint(certPath) { * Check if certificate is already installed in system store */ async function checkCertInstalled(certPath) { - if (IS_WIN) { - return checkCertInstalledWindows(certPath); - } - return checkCertInstalledMac(certPath); + if (IS_WIN) return checkCertInstalledWindows(certPath); + if (IS_MAC) return checkCertInstalledMac(certPath); + return checkCertInstalledLinux(); } function checkCertInstalledMac(certPath) { @@ -60,8 +61,10 @@ async function installCert(sudoPassword, certPath) { if (IS_WIN) { await installCertWindows(certPath); - } else { + } else if (IS_MAC) { await installCertMac(sudoPassword, certPath); + } else { + await installCertLinux(sudoPassword, certPath); } } @@ -103,8 +106,10 @@ async function uninstallCert(sudoPassword, certPath) { if (IS_WIN) { await uninstallCertWindows(); - } else { + } else if (IS_MAC) { await uninstallCertMac(sudoPassword, certPath); + } else { + await uninstallCertLinux(sudoPassword); } } @@ -133,4 +138,32 @@ async function uninstallCertWindows() { }); } +function checkCertInstalledLinux() { + const certFile = `${LINUX_CERT_DIR}/9router-mitm.crt`; + return Promise.resolve(fs.existsSync(certFile)); +} + +async function installCertLinux(sudoPassword, certPath) { + const destFile = `${LINUX_CERT_DIR}/9router-mitm.crt`; + // Try update-ca-certificates (Debian/Ubuntu), fallback to update-ca-trust (Fedora/RHEL) + const cmd = `cp "${certPath}" "${destFile}" && (update-ca-certificates 2>/dev/null || update-ca-trust 2>/dev/null || true)`; + try { + await execWithPassword(cmd, sudoPassword); + console.log("✅ Installed certificate to Linux trust store"); + } catch (error) { + throw new Error("Certificate install failed"); + } +} + +async function uninstallCertLinux(sudoPassword) { + const destFile = `${LINUX_CERT_DIR}/9router-mitm.crt`; + const cmd = `rm -f "${destFile}" && (update-ca-certificates 2>/dev/null || update-ca-trust 2>/dev/null || true)`; + try { + await execWithPassword(cmd, sudoPassword); + console.log("✅ Uninstalled certificate from Linux trust store"); + } catch (error) { + throw new Error("Failed to uninstall certificate"); + } +} + module.exports = { installCert, uninstallCert, checkCertInstalled }; diff --git a/src/mitm/dns/dnsConfig.js b/src/mitm/dns/dnsConfig.js index da76ca0..b48433a 100644 --- a/src/mitm/dns/dnsConfig.js +++ b/src/mitm/dns/dnsConfig.js @@ -1,9 +1,11 @@ const { exec, spawn } = require("child_process"); const fs = require("fs"); const path = require("path"); +const os = require("os"); const TARGET_HOST = "daily-cloudcode-pa.googleapis.com"; const IS_WIN = process.platform === "win32"; +const IS_MAC = process.platform === "darwin"; const HOSTS_FILE = IS_WIN ? path.join(process.env.SystemRoot || "C:\\Windows", "System32", "drivers", "etc", "hosts") : "/etc/hosts"; @@ -81,8 +83,11 @@ async function addDNSEntry(sudoPassword) { // Flush DNS cache if (IS_WIN) { await execElevatedWindows("ipconfig /flushdns"); - } else { + } else if (IS_MAC) { await execWithPassword("dscacheutil -flushcache && killall -HUP mDNSResponder", sudoPassword); + } else { + // Linux: try systemd-resolved, fall back silently + await execWithPassword("resolvectl flush-caches 2>/dev/null || true", sudoPassword); } console.log(`✅ Added DNS entry: ${entry}`); } catch (error) { @@ -102,23 +107,37 @@ async function removeDNSEntry(sudoPassword) { try { if (IS_WIN) { - // Windows: read, filter, write back via elevated PowerShell - const psScript = `(Get-Content '${HOSTS_FILE}') | Where-Object { $_ -notmatch '${TARGET_HOST}' } | Set-Content '${HOSTS_FILE}'`; - const psCommand = `Start-Process powershell -ArgumentList '-Command','${psScript.replace(/'/g, "''")}' -Verb RunAs -Wait`; + // Read in Node, filter, write to temp file, then elevated-copy over hosts + const content = fs.readFileSync(HOSTS_FILE, "utf8"); + const filtered = content.split(/\r?\n/).filter(l => !l.includes(TARGET_HOST)).join("\r\n"); + if (!filtered.trim() && content.trim()) { + throw new Error("Filtered hosts content is empty, aborting to prevent data loss"); + } + const tmpFile = path.join(os.tmpdir(), "hosts_filtered.tmp"); + fs.writeFileSync(tmpFile, filtered, "utf8"); + // Use elevated cmd to copy temp file over hosts (safe: original untouched until copy succeeds) + const psCommand = `Start-Process cmd -ArgumentList '/c','copy /Y "${tmpFile}" "${HOSTS_FILE}"' -Verb RunAs -Wait`; await new Promise((resolve, reject) => { exec(`powershell -Command "${psCommand}"`, (error) => { + try { fs.unlinkSync(tmpFile); } catch { /* ignore */ } if (error) reject(new Error(`Failed to remove DNS entry: ${error.message}`)); else resolve(); }); }); } else { - await execWithPassword(`sed -i '' '/${TARGET_HOST}/d' ${HOSTS_FILE}`, sudoPassword); + // sed -i '' is macOS syntax; Linux uses sed -i without the empty string arg + const sedCmd = IS_MAC + ? `sed -i '' '/${TARGET_HOST}/d' ${HOSTS_FILE}` + : `sed -i '/${TARGET_HOST}/d' ${HOSTS_FILE}`; + await execWithPassword(sedCmd, sudoPassword); } // Flush DNS cache if (IS_WIN) { await execElevatedWindows("ipconfig /flushdns"); - } else { + } else if (IS_MAC) { await execWithPassword("dscacheutil -flushcache && killall -HUP mDNSResponder", sudoPassword); + } else { + await execWithPassword("resolvectl flush-caches 2>/dev/null || true", sudoPassword); } console.log(`✅ Removed DNS entry for ${TARGET_HOST}`); } catch (error) { diff --git a/src/mitm/manager.js b/src/mitm/manager.js index c3eb05e..e1a9f6d 100644 --- a/src/mitm/manager.js +++ b/src/mitm/manager.js @@ -389,13 +389,14 @@ async function startMitm(apiKey, sudoPassword) { console.log("Starting MITM server..."); if (IS_WIN) { - // Launch elevated node via PowerShell RunAs (triggers UAC prompt) - const nodePath = process.execPath.replace(/'/g, "''"); - const serverPath = SERVER_PATH.replace(/'/g, "''"); + // Use cmd /c to set env vars inline before launching node (env vars survive RunAs) + const nodePath = process.execPath.replace(/"/g, '\\"'); + const serverPath = SERVER_PATH.replace(/"/g, '\\"'); + const cmdLine = `set ROUTER_API_KEY=${apiKey}&& set NODE_ENV=production&& "${nodePath}" "${serverPath}"`; serverProcess = spawn("powershell", [ "-NoProfile", "-Command", - `$env:ROUTER_API_KEY='${apiKey}'; $env:NODE_ENV='production'; Start-Process '${nodePath}' -ArgumentList '''${serverPath}''' -Verb RunAs -WindowStyle Hidden` - ], { stdio: "ignore", env: process.env }); + `Start-Process cmd -ArgumentList '/c','${cmdLine.replace(/'/g, "''")}' -Verb RunAs -WindowStyle Hidden` + ], { stdio: "ignore" }); } else { // sudo -S: read password from stdin, -E: preserve env vars // Pass ROUTER_API_KEY inline via env=... wrapper to avoid sudo stripping env @@ -413,23 +414,25 @@ async function startMitm(apiKey, sudoPassword) { fs.writeFileSync(PID_FILE, String(serverPid)); let startError = null; - serverProcess.stdout.on("data", (data) => { - console.log(`[MITM Server] ${data.toString().trim()}`); - }); - serverProcess.stderr.on("data", (data) => { - const msg = data.toString().trim(); - // Capture meaningful errors (ignore sudo password prompt noise) - if (msg && !msg.includes("Password:") && !msg.includes("password for")) { - console.error(`[MITM Server Error] ${msg}`); - startError = msg; - } - }); - serverProcess.on("exit", (code) => { - console.log(`MITM server exited with code ${code}`); - serverProcess = null; - serverPid = null; - try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ } - }); + if (!IS_WIN) { + serverProcess.stdout.on("data", (data) => { + console.log(`[MITM Server] ${data.toString().trim()}`); + }); + serverProcess.stderr.on("data", (data) => { + const msg = data.toString().trim(); + // Capture meaningful errors (ignore sudo password prompt noise) + if (msg && !msg.includes("Password:") && !msg.includes("password for")) { + console.error(`[MITM Server Error] ${msg}`); + startError = msg; + } + }); + serverProcess.on("exit", (code) => { + console.log(`MITM server exited with code ${code}`); + serverProcess = null; + serverPid = null; + try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ } + }); + } // Wait for server to be ready by polling health endpoint const health = await pollMitmHealth(IS_WIN ? 12000 : 8000);