Feat : Add support for the new "alicode-intl" provider
This commit is contained in:
@@ -15,6 +15,47 @@ const HOSTS_FILE = IS_WIN
|
||||
? path.join(process.env.SystemRoot || "C:\\Windows", "System32", "drivers", "etc", "hosts")
|
||||
: "/etc/hosts";
|
||||
|
||||
/**
|
||||
* Execute elevated PowerShell script on Windows via Start-Process -Verb RunAs.
|
||||
* Only UAC consent dialog appears, no CMD/PS window popup.
|
||||
*/
|
||||
function executeElevatedPowerShell(psScriptPath, timeoutMs = 30000) {
|
||||
const flagFile = path.join(os.tmpdir(), `ps_done_${Date.now()}.flag`);
|
||||
const psSQ = (s) => s.replace(/'/g, "''");
|
||||
|
||||
let psContent = fs.readFileSync(psScriptPath, "utf8");
|
||||
psContent += `\nSet-Content -Path '${psSQ(flagFile)}' -Value 'done' -Encoding UTF8\n`;
|
||||
fs.writeFileSync(psScriptPath, psContent, "utf8");
|
||||
|
||||
const outerCmd = `Start-Process powershell -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-WindowStyle','Hidden','-File','${psSQ(psScriptPath)}' -Verb RunAs -WindowStyle Hidden`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let settled = false;
|
||||
const settle = (fn, arg) => { if (!settled) { settled = true; fn(arg); } };
|
||||
|
||||
exec(
|
||||
`powershell -NoProfile -NonInteractive -WindowStyle Hidden -Command "${outerCmd}"`,
|
||||
{ windowsHide: true },
|
||||
() => {}
|
||||
);
|
||||
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
const poll = () => {
|
||||
if (settled) return;
|
||||
if (fs.existsSync(flagFile)) {
|
||||
try { fs.unlinkSync(flagFile); fs.unlinkSync(psScriptPath); } catch { /* ignore */ }
|
||||
return settle(resolve);
|
||||
}
|
||||
if (Date.now() > deadline) {
|
||||
try { fs.unlinkSync(psScriptPath); } catch { /* ignore */ }
|
||||
return settle(reject, new Error("Timed out waiting for UAC confirmation"));
|
||||
}
|
||||
setTimeout(poll, 500);
|
||||
};
|
||||
setTimeout(poll, 300);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute command with sudo password via stdin (macOS/Linux only)
|
||||
*/
|
||||
@@ -99,18 +140,37 @@ async function addDNSEntry(tool, sudoPassword) {
|
||||
try {
|
||||
if (IS_WIN) {
|
||||
const hostsPath = HOSTS_FILE.replace(/'/g, "''");
|
||||
const addLines = entriesToAdd.map(h =>
|
||||
`$hc = Get-Content -Path '${hostsPath}' -Raw -ErrorAction SilentlyContinue; if ($hc -notmatch '${h}') { Add-Content -Path '${hostsPath}' -Value '127.0.0.1 ${h}' -Encoding UTF8 }`
|
||||
).join("; ");
|
||||
const psScript = `${addLines}; ipconfig /flushdns | Out-Null`;
|
||||
await new Promise((resolve, reject) => {
|
||||
const escaped = psScript.replace(/"/g, '\\"');
|
||||
exec(
|
||||
`powershell -NonInteractive -WindowStyle Hidden -Command "Start-Process powershell -ArgumentList '-NonInteractive -WindowStyle Hidden -Command \\"${escaped}\\"' -Verb RunAs -Wait"`,
|
||||
{ windowsHide: true },
|
||||
(error) => { if (error) reject(new Error(`Failed to add DNS: ${error.message}`)); else resolve(); }
|
||||
);
|
||||
});
|
||||
|
||||
// Build PowerShell script with proper error handling
|
||||
const scriptLines = [];
|
||||
scriptLines.push(`$ErrorActionPreference = 'Stop'`);
|
||||
scriptLines.push(`$hostsPath = '${hostsPath}'`);
|
||||
scriptLines.push(`try {`);
|
||||
scriptLines.push(` $hostsContent = Get-Content -Path $hostsPath -Raw -ErrorAction SilentlyContinue`);
|
||||
scriptLines.push(` if (-not $hostsContent) { $hostsContent = '' }`);
|
||||
|
||||
for (const host of entriesToAdd) {
|
||||
// Escape special regex chars in hostname
|
||||
const escapedHost = host.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
scriptLines.push(` if ($hostsContent -notmatch '${escapedHost}') {`);
|
||||
scriptLines.push(` Add-Content -Path $hostsPath -Value '127.0.0.1 ${host}' -Encoding UTF8 -ErrorAction Stop`);
|
||||
scriptLines.push(` Write-Host "Added DNS entry: ${host}"`);
|
||||
scriptLines.push(` } else {`);
|
||||
scriptLines.push(` Write-Host "DNS entry already exists: ${host}"`);
|
||||
scriptLines.push(` }`);
|
||||
}
|
||||
|
||||
scriptLines.push(` ipconfig /flushdns | Out-Null`);
|
||||
scriptLines.push(`} catch {`);
|
||||
scriptLines.push(` Write-Error "Failed to add DNS: $_"`);
|
||||
scriptLines.push(` exit 1`);
|
||||
scriptLines.push(`}`);
|
||||
|
||||
const psScript = scriptLines.join("\n");
|
||||
const tmpPs1 = path.join(os.tmpdir(), `mitm_dns_add_${Date.now()}.ps1`);
|
||||
fs.writeFileSync(tmpPs1, psScript, "utf8");
|
||||
|
||||
await executeElevatedPowerShell(tmpPs1, 30000);
|
||||
} else {
|
||||
await execWithPassword(`echo "${entries}" >> ${HOSTS_FILE}`, sudoPassword);
|
||||
await flushDNS(sudoPassword);
|
||||
@@ -139,23 +199,35 @@ async function removeDNSEntry(tool, sudoPassword) {
|
||||
if (IS_WIN) {
|
||||
const content = fs.readFileSync(HOSTS_FILE, "utf8");
|
||||
const filtered = content.split(/\r?\n/).filter(l => !entriesToRemove.some(h => l.includes(h))).join("\r\n");
|
||||
const tmpFile = path.join(os.tmpdir(), "hosts_filtered.tmp");
|
||||
const tmpFile = path.join(os.tmpdir(), `hosts_filtered_${Date.now()}.tmp`);
|
||||
fs.writeFileSync(tmpFile, filtered, "utf8");
|
||||
|
||||
const tmpEsc = tmpFile.replace(/'/g, "''");
|
||||
const hostsEsc = HOSTS_FILE.replace(/'/g, "''");
|
||||
const psScript = `Copy-Item -Path '${tmpEsc}' -Destination '${hostsEsc}' -Force; ipconfig /flushdns | Out-Null; Remove-Item '${tmpEsc}' -ErrorAction SilentlyContinue`;
|
||||
await new Promise((resolve, reject) => {
|
||||
const escaped = psScript.replace(/"/g, '\\"');
|
||||
exec(
|
||||
`powershell -NonInteractive -WindowStyle Hidden -Command "Start-Process powershell -ArgumentList '-NonInteractive -WindowStyle Hidden -Command \\"${escaped}\\"' -Verb RunAs -Wait"`,
|
||||
{ windowsHide: true },
|
||||
(error) => {
|
||||
try { fs.unlinkSync(tmpFile); } catch { /* ignore */ }
|
||||
if (error) reject(new Error(`Failed to remove DNS: ${error.message}`));
|
||||
else resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Build PowerShell script with proper error handling
|
||||
const scriptLines = [];
|
||||
scriptLines.push(`$ErrorActionPreference = 'Stop'`);
|
||||
scriptLines.push(`try {`);
|
||||
scriptLines.push(` Copy-Item -Path '${tmpEsc}' -Destination '${hostsEsc}' -Force -ErrorAction Stop`);
|
||||
scriptLines.push(` Write-Host "Hosts file updated successfully"`);
|
||||
scriptLines.push(` ipconfig /flushdns | Out-Null`);
|
||||
scriptLines.push(` Write-Host "DNS cache flushed"`);
|
||||
scriptLines.push(` Remove-Item '${tmpEsc}' -ErrorAction SilentlyContinue`);
|
||||
scriptLines.push(`} catch {`);
|
||||
scriptLines.push(` Write-Error "Failed to remove DNS: $_"`);
|
||||
scriptLines.push(` Remove-Item '${tmpEsc}' -ErrorAction SilentlyContinue`);
|
||||
scriptLines.push(` exit 1`);
|
||||
scriptLines.push(`}`);
|
||||
|
||||
const psScript = scriptLines.join("\n");
|
||||
const tmpPs1 = path.join(os.tmpdir(), `mitm_dns_remove_${Date.now()}.ps1`);
|
||||
fs.writeFileSync(tmpPs1, psScript, "utf8");
|
||||
|
||||
await executeElevatedPowerShell(tmpPs1, 30000);
|
||||
|
||||
// Cleanup temp file if still exists
|
||||
try { fs.unlinkSync(tmpFile); } catch { /* ignore */ }
|
||||
} else {
|
||||
for (const host of entriesToRemove) {
|
||||
const sedCmd = IS_MAC
|
||||
@@ -191,6 +263,7 @@ module.exports = {
|
||||
removeDNSEntry,
|
||||
removeAllDNSEntries,
|
||||
execWithPassword,
|
||||
executeElevatedPowerShell,
|
||||
checkDNSEntry,
|
||||
checkAllDNSStatus,
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ const os = require("os");
|
||||
const net = require("net");
|
||||
const https = require("https");
|
||||
const crypto = require("crypto");
|
||||
const { addDNSEntry, removeDNSEntry, removeAllDNSEntries, checkAllDNSStatus } = require("./dns/dnsConfig");
|
||||
const { addDNSEntry, removeDNSEntry, removeAllDNSEntries, checkAllDNSStatus, executeElevatedPowerShell, TOOL_HOSTS } = require("./dns/dnsConfig");
|
||||
|
||||
const IS_WIN = process.platform === "win32";
|
||||
const { generateCert } = require("./cert/generate");
|
||||
@@ -345,49 +345,23 @@ async function startServer(apiKey, sudoPassword) {
|
||||
|
||||
// Step 2: Spawn server (Root CA already installed in Step 1.5)
|
||||
if (IS_WIN) {
|
||||
const hostsFile = path.join(process.env.SystemRoot || "C:\\Windows", "System32", "drivers", "etc", "hosts");
|
||||
const flagFile = path.join(os.tmpdir(), `mitm_ready_${Date.now()}.flag`);
|
||||
const psSQ = (s) => s.replace(/'/g, "''");
|
||||
const nodePs = psSQ(process.execPath);
|
||||
const serverPs = psSQ(SERVER_PATH);
|
||||
const flagPs = psSQ(flagFile);
|
||||
|
||||
const psScript = [
|
||||
`$ErrorActionPreference = 'Stop'`,
|
||||
`$conn = Get-NetTCPConnection -LocalPort 443 -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1`,
|
||||
`if ($conn -and $conn.OwningProcess -gt 4) { Stop-Process -Id $conn.OwningProcess -Force -ErrorAction SilentlyContinue }`,
|
||||
`Start-Sleep -Milliseconds 500`,
|
||||
`$nodeCmd = 'set ROUTER_API_KEY=${psSQ(apiKey)}&& set NODE_ENV=production&& "${nodePs}" "${serverPs}"'`,
|
||||
`Start-Process cmd -ArgumentList '/c',$nodeCmd -WindowStyle Hidden`,
|
||||
`Start-Sleep -Milliseconds 500`,
|
||||
`Set-Content -Path '${flagPs}' -Value 'ready' -Encoding UTF8`,
|
||||
].join("\n");
|
||||
|
||||
const tmpPs1 = path.join(os.tmpdir(), `mitm_start_${Date.now()}.ps1`);
|
||||
fs.writeFileSync(tmpPs1, psScript, "utf8");
|
||||
const vbs = [
|
||||
`Set oShell = CreateObject("Shell.Application")`,
|
||||
`Dim ps`,
|
||||
`ps = Chr(34) & "powershell.exe" & Chr(34)`,
|
||||
`Dim args`,
|
||||
`args = "-NoProfile -ExecutionPolicy Bypass -File " & Chr(34) & "${tmpPs1}" & Chr(34)`,
|
||||
`oShell.ShellExecute ps, args, "", "runas", 1`,
|
||||
].join("\r\n");
|
||||
const tmpVbs = path.join(os.tmpdir(), `mitm_uac_${Date.now()}.vbs`);
|
||||
fs.writeFileSync(tmpVbs, vbs, "utf8");
|
||||
spawn("wscript.exe", [tmpVbs], { stdio: "ignore", windowsHide: true, detached: true }).unref();
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const deadline = Date.now() + 90000;
|
||||
const poll = () => {
|
||||
if (fs.existsSync(flagFile)) {
|
||||
try { fs.unlinkSync(flagFile); fs.unlinkSync(tmpPs1); fs.unlinkSync(tmpVbs); } catch { /* ignore */ }
|
||||
return resolve();
|
||||
}
|
||||
if (Date.now() > deadline) return reject(new Error("Timed out waiting for UAC confirmation."));
|
||||
setTimeout(poll, 500);
|
||||
};
|
||||
poll();
|
||||
});
|
||||
await executeElevatedPowerShell(tmpPs1, 90000);
|
||||
|
||||
if (_updateSettings) await _updateSettings({ mitmCertInstalled: true }).catch(() => { });
|
||||
} else {
|
||||
@@ -451,38 +425,27 @@ async function startServer(apiKey, sudoPassword) {
|
||||
* Stop MITM server — removes ALL tool DNS entries first, then kills server
|
||||
*/
|
||||
async function stopServer(sudoPassword) {
|
||||
// Remove all DNS entries first (before killing server)
|
||||
console.log("[MITM] Removing all DNS entries before stopping server...");
|
||||
await removeAllDNSEntries(sudoPassword);
|
||||
console.log("[MITM] Stopping server...");
|
||||
|
||||
// Kill server process
|
||||
const proc = serverProcess;
|
||||
if (proc && !proc.killed) {
|
||||
console.log("Stopping MITM server...");
|
||||
killProcess(proc.pid, false, sudoPassword);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
if (isProcessAlive(proc.pid)) killProcess(proc.pid, true, sudoPassword);
|
||||
serverProcess = null;
|
||||
serverPid = null;
|
||||
} else {
|
||||
try {
|
||||
if (fs.existsSync(PID_FILE)) {
|
||||
const savedPid = parseInt(fs.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
||||
if (savedPid && isProcessAlive(savedPid)) {
|
||||
console.log(`Killing MITM server (PID: ${savedPid})...`);
|
||||
killProcess(savedPid, false, sudoPassword);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
if (isProcessAlive(savedPid)) killProcess(savedPid, true, sudoPassword);
|
||||
}
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
serverProcess = null;
|
||||
serverPid = null;
|
||||
const pidToKill = proc && !proc.killed
|
||||
? proc.pid
|
||||
: (() => { try { return parseInt(fs.readFileSync(PID_FILE, "utf-8").trim(), 10); } catch { return null; } })();
|
||||
|
||||
if (pidToKill && isProcessAlive(pidToKill)) {
|
||||
console.log(`Killing MITM server (PID: ${pidToKill})...`);
|
||||
killProcess(pidToKill, false, sudoPassword);
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
if (isProcessAlive(pidToKill)) killProcess(pidToKill, true, sudoPassword);
|
||||
}
|
||||
serverProcess = null;
|
||||
serverPid = null;
|
||||
|
||||
if (IS_WIN) {
|
||||
// Single elevated script: clean DNS + flush — 1 UAC prompt only
|
||||
const hostsFile = path.join(process.env.SystemRoot || "C:\\Windows", "System32", "drivers", "etc", "hosts");
|
||||
const psSQ = (s) => s.replace(/'/g, "''");
|
||||
const { TOOL_HOSTS } = require("./dns/dnsConfig");
|
||||
const allHosts = Object.values(TOOL_HOSTS).flat();
|
||||
|
||||
let hostsContent = "";
|
||||
@@ -490,41 +453,25 @@ async function stopServer(sudoPassword) {
|
||||
const filtered = hostsContent.split(/\r?\n/)
|
||||
.filter(l => !allHosts.some(h => l.includes(h)))
|
||||
.join("\r\n");
|
||||
const tmpHosts = path.join(os.tmpdir(), "mitm_hosts_clean.tmp");
|
||||
const tmpHosts = path.join(os.tmpdir(), `mitm_hosts_clean_${Date.now()}.tmp`);
|
||||
fs.writeFileSync(tmpHosts, filtered, "utf8");
|
||||
|
||||
const flagFile = path.join(os.tmpdir(), "mitm_stop_done.flag");
|
||||
const psScript = [
|
||||
`Copy-Item -Path '${psSQ(tmpHosts)}' -Destination '${psSQ(hostsFile)}' -Force`,
|
||||
`& ipconfig /flushdns | Out-Null`,
|
||||
`Remove-Item '${psSQ(tmpHosts)}' -ErrorAction SilentlyContinue`,
|
||||
`Set-Content -Path '${psSQ(flagFile)}' -Value 'done' -Encoding UTF8`,
|
||||
`$ErrorActionPreference = 'Stop'`,
|
||||
`try {`,
|
||||
` Copy-Item -Path '${psSQ(tmpHosts)}' -Destination '${psSQ(hostsFile)}' -Force -ErrorAction Stop`,
|
||||
` ipconfig /flushdns | Out-Null`,
|
||||
` Remove-Item '${psSQ(tmpHosts)}' -ErrorAction SilentlyContinue`,
|
||||
`} catch {`,
|
||||
` Remove-Item '${psSQ(tmpHosts)}' -ErrorAction SilentlyContinue`,
|
||||
`}`,
|
||||
].join("\n");
|
||||
const tmpPs1 = path.join(os.tmpdir(), "mitm_stop.ps1");
|
||||
|
||||
const tmpPs1 = path.join(os.tmpdir(), `mitm_stop_${Date.now()}.ps1`);
|
||||
fs.writeFileSync(tmpPs1, psScript, "utf8");
|
||||
|
||||
const vbs = [
|
||||
`Set oShell = CreateObject("Shell.Application")`,
|
||||
`Dim args`,
|
||||
`args = "-NoProfile -ExecutionPolicy Bypass -File " & Chr(34) & "${tmpPs1}" & Chr(34)`,
|
||||
`oShell.ShellExecute "powershell.exe", args, "", "runas", 1`,
|
||||
].join("\r\n");
|
||||
const tmpVbs = path.join(os.tmpdir(), "mitm_stop_uac.vbs");
|
||||
fs.writeFileSync(tmpVbs, vbs, "utf8");
|
||||
spawn("wscript.exe", [tmpVbs], { stdio: "ignore", windowsHide: true, detached: true }).unref();
|
||||
|
||||
await new Promise((resolve) => {
|
||||
const deadline = Date.now() + 30000;
|
||||
const poll = () => {
|
||||
if (fs.existsSync(flagFile)) {
|
||||
try { fs.unlinkSync(flagFile); fs.unlinkSync(tmpPs1); fs.unlinkSync(tmpVbs); } catch { /* ignore */ }
|
||||
return resolve();
|
||||
}
|
||||
if (Date.now() > deadline) return resolve();
|
||||
setTimeout(poll, 500);
|
||||
};
|
||||
poll();
|
||||
});
|
||||
await executeElevatedPowerShell(tmpPs1, 30000);
|
||||
} else {
|
||||
await removeAllDNSEntries(sudoPassword);
|
||||
}
|
||||
|
||||
try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ }
|
||||
|
||||
Reference in New Issue
Block a user