@miraj181/ipingyou 2.0.10 → 2.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli.js +10 -15
- package/src/lib/broker.js +144 -0
- package/src/lib/checksum.js +12 -0
- package/src/lib/cleanup.js +37 -15
- package/src/lib/config.js +7 -3
- package/src/lib/path-browser.js +232 -0
- package/src/lib/ssh.js +52 -0
- package/src/lib/tunnel.js +66 -0
- package/src/modes/client.js +48 -241
- package/src/modes/host.js +70 -155
- package/src/server.js +6 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { createSpinner, tunnelSpinner } from './animations.js';
|
|
4
|
+
import { killProcessTree, trackPID, untrackPID } from './cleanup.js';
|
|
5
|
+
|
|
6
|
+
export async function spawnTunnelSupervised(targetUrl, onUrlGenerated) {
|
|
7
|
+
let isShuttingDown = false;
|
|
8
|
+
let activeChild = null;
|
|
9
|
+
|
|
10
|
+
const loop = async () => {
|
|
11
|
+
while (!isShuttingDown) {
|
|
12
|
+
const spinner = createSpinner('Starting Cloudflare tunnel...', tunnelSpinner).start();
|
|
13
|
+
|
|
14
|
+
await new Promise((resolve) => {
|
|
15
|
+
activeChild = execa('cloudflared', ['tunnel', '--url', targetUrl], {
|
|
16
|
+
reject: false,
|
|
17
|
+
all: true,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
trackPID(activeChild.pid);
|
|
21
|
+
let resolved = false;
|
|
22
|
+
|
|
23
|
+
activeChild.all.on('data', (chunk) => {
|
|
24
|
+
const text = chunk.toString();
|
|
25
|
+
const match = text.match(/https:\/\/[-0-9a-z]+\.trycloudflare\.com/);
|
|
26
|
+
if (match && !resolved) {
|
|
27
|
+
resolved = true;
|
|
28
|
+
spinner.succeed(`Tunnel active: ${chalk.cyan(match[0])}`);
|
|
29
|
+
onUrlGenerated(match[0]);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
activeChild.on('exit', (code) => {
|
|
34
|
+
untrackPID(activeChild.pid);
|
|
35
|
+
if (!resolved) {
|
|
36
|
+
spinner.fail('Cloudflare tunnel exited before generating URL');
|
|
37
|
+
} else if (!isShuttingDown) {
|
|
38
|
+
console.log(chalk.yellow(`\n ⚠️ Tunnel disconnected (code ${code}). Restarting...`));
|
|
39
|
+
}
|
|
40
|
+
resolve();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
activeChild.on('error', (err) => {
|
|
44
|
+
untrackPID(activeChild.pid);
|
|
45
|
+
spinner.fail(`Tunnel error: ${err.message}`);
|
|
46
|
+
resolve();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (!isShuttingDown) {
|
|
51
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
loop();
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
kill: () => {
|
|
60
|
+
isShuttingDown = true;
|
|
61
|
+
if (activeChild) {
|
|
62
|
+
killProcessTree(activeChild.pid).finally(() => untrackPID(activeChild.pid));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
package/src/modes/client.js
CHANGED
|
@@ -18,132 +18,17 @@ import inquirer from 'inquirer';
|
|
|
18
18
|
import fs from 'node:fs';
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import os from 'node:os';
|
|
21
|
-
import
|
|
22
|
-
import { decrypt, encrypt } from '../lib/crypto.js';
|
|
23
|
-
import { trackPID, untrackPID, addCleanupHook } from '../lib/cleanup.js';
|
|
21
|
+
import { cleanupAll, trackPID, untrackPID, addCleanupHook } from '../lib/cleanup.js';
|
|
24
22
|
import { createSpinner, sshSpinner, networkSpinner, fileTransferSpinner, showConnectionTrace, simulateTransferProgress } from '../lib/animations.js';
|
|
25
23
|
import { getConfig, saveAlias } from '../lib/config.js';
|
|
24
|
+
import { pushTelemetry, resolveUID } from '../lib/broker.js';
|
|
25
|
+
import { calculateChecksum } from '../lib/checksum.js';
|
|
26
|
+
import { promptLocalPath, promptRemotePath } from '../lib/path-browser.js';
|
|
27
|
+
import { buildSshArgs, extractHostname, formatScpRemotePath, getSshControlOptions, quoteRemoteShell } from '../lib/ssh.js';
|
|
26
28
|
import open from 'open';
|
|
27
29
|
|
|
28
30
|
let BROKER_URL = process.env.BROKER_URL || 'https://ipingyou.onrender.com';
|
|
29
31
|
|
|
30
|
-
/**
|
|
31
|
-
* Gather client hardware telemetry and send securely to broker.
|
|
32
|
-
*/
|
|
33
|
-
async function pushTelemetry(uid, password, username) {
|
|
34
|
-
try {
|
|
35
|
-
let publicIp = 'Unknown';
|
|
36
|
-
try {
|
|
37
|
-
publicIp = await fetch('https://api.ipify.org').then(r => r.text());
|
|
38
|
-
} catch {}
|
|
39
|
-
|
|
40
|
-
const telemetry = {
|
|
41
|
-
username,
|
|
42
|
-
ip: publicIp,
|
|
43
|
-
os: `${os.type()} ${os.release()} (${os.arch()})`,
|
|
44
|
-
cpu: os.cpus()[0]?.model || 'Unknown CPU',
|
|
45
|
-
ram: `${Math.round(os.totalmem() / 1024 / 1024 / 1024)} GB`,
|
|
46
|
-
time: new Date().toLocaleTimeString()
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const payloadStr = JSON.stringify(telemetry);
|
|
50
|
-
// Use the same AES password logic to securely encrypt the telemetry blob
|
|
51
|
-
const { iv, ciphertext, salt } = encrypt(payloadStr, password);
|
|
52
|
-
|
|
53
|
-
await fetch(`${BROKER_URL}/client-info/${uid}`, {
|
|
54
|
-
method: 'POST',
|
|
55
|
-
headers: { 'Content-Type': 'application/json' },
|
|
56
|
-
body: JSON.stringify({ iv, ciphertext, salt }),
|
|
57
|
-
});
|
|
58
|
-
} catch {
|
|
59
|
-
// Fail silently, telemetry is optional
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Resolve a UID to a tunnel URL via the broker.
|
|
65
|
-
* The broker returns an encrypted blob; we decrypt locally.
|
|
66
|
-
*
|
|
67
|
-
* @param {string} uid
|
|
68
|
-
* @param {string} password
|
|
69
|
-
* @param {boolean} silent If true, suppresses the spinner animations
|
|
70
|
-
* @returns {object|null} The decrypted payload
|
|
71
|
-
*/
|
|
72
|
-
async function resolveUID(uid, password, silent = false) {
|
|
73
|
-
const spinner = !silent ? createSpinner(`Resolving UID ${chalk.cyan(uid)}...`, networkSpinner).start() : null;
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
const res = await fetch(`${BROKER_URL}/resolve/${uid}`);
|
|
77
|
-
|
|
78
|
-
if (res.status === 404) {
|
|
79
|
-
if (spinner) spinner.fail('UID not found — the host may not be online or the session expired');
|
|
80
|
-
else console.error(chalk.red(' ❌ UID not found or expired.'));
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
if (res.status === 410) {
|
|
84
|
-
if (spinner) spinner.fail('UID has expired — ask the host for a new session');
|
|
85
|
-
else console.error(chalk.red(' ❌ UID expired.'));
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
if (!res.ok) {
|
|
89
|
-
const data = await res.json().catch(() => ({}));
|
|
90
|
-
throw new Error(data.error || `HTTP ${res.status}`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const data = await res.json();
|
|
94
|
-
|
|
95
|
-
if (!data.iv || !data.ciphertext || !data.salt) {
|
|
96
|
-
if (spinner) spinner.fail('Broker returned invalid response — missing encrypted data or salt');
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (spinner) spinner.text = `Decrypting tunnel URL locally...`;
|
|
101
|
-
|
|
102
|
-
// Simulate decryption delay for effect only if not silent
|
|
103
|
-
if (!silent) await new Promise(r => setTimeout(r, 600));
|
|
104
|
-
|
|
105
|
-
// Decrypt locally
|
|
106
|
-
let decryptedPayload;
|
|
107
|
-
try {
|
|
108
|
-
decryptedPayload = decrypt(data.iv, data.ciphertext, password, data.salt);
|
|
109
|
-
} catch (decryptErr) {
|
|
110
|
-
if (spinner) spinner.fail('Decryption failed — incorrect password or corrupted data');
|
|
111
|
-
if (!spinner) console.error(chalk.red(' ❌ Error: Could not decrypt tunnel data. Incorrect password.'));
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
let payloadConfig;
|
|
116
|
-
try {
|
|
117
|
-
payloadConfig = JSON.parse(decryptedPayload);
|
|
118
|
-
// Backwards compatibility if host sent raw URL instead of JSON
|
|
119
|
-
if (typeof payloadConfig !== 'object' || !payloadConfig.url) {
|
|
120
|
-
payloadConfig = { url: decryptedPayload, type: 'ssh' };
|
|
121
|
-
}
|
|
122
|
-
} catch {
|
|
123
|
-
payloadConfig = { url: decryptedPayload, type: 'ssh' };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (!payloadConfig.url.startsWith('https://')) {
|
|
127
|
-
if (spinner) spinner.fail('Decrypted data is not a valid tunnel URL (incorrect password)');
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (spinner) spinner.succeed(`Resolved: ${chalk.dim(payloadConfig.url)} ${chalk.green('[decrypted locally]')}`);
|
|
132
|
-
return payloadConfig;
|
|
133
|
-
} catch (err) {
|
|
134
|
-
if (spinner) spinner.fail(`Broker lookup failed: ${err.message}`);
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function extractHostname(url) {
|
|
140
|
-
try {
|
|
141
|
-
return new URL(url).hostname;
|
|
142
|
-
} catch {
|
|
143
|
-
return url.replace(/^https?:\/\//, '').replace(/\/$/, '');
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
32
|
async function promptUsername() {
|
|
148
33
|
const { username } = await inquirer.prompt([
|
|
149
34
|
{
|
|
@@ -167,24 +52,16 @@ async function connectSSH(username, hostname, privateKeyPath) {
|
|
|
167
52
|
|
|
168
53
|
await showConnectionTrace('Local', 'Remote SSH');
|
|
169
54
|
|
|
170
|
-
const proxyCommand = `cloudflared access tcp --hostname ${hostname}`;
|
|
171
|
-
|
|
172
55
|
try {
|
|
173
56
|
const spinner = createSpinner('Handshaking...', sshSpinner).start();
|
|
174
57
|
await new Promise(r => setTimeout(r, 800));
|
|
175
58
|
spinner.succeed('Connection established! Handing over to terminal...');
|
|
176
59
|
console.log('');
|
|
177
60
|
|
|
178
|
-
const sshArgs = [
|
|
179
|
-
'-o', `ProxyCommand=${proxyCommand}`,
|
|
180
|
-
'-o', 'StrictHostKeyChecking=accept-new',
|
|
61
|
+
const sshArgs = buildSshArgs(hostname, privateKeyPath, [
|
|
181
62
|
'-o', 'ServerAliveInterval=30',
|
|
182
63
|
'-o', 'ServerAliveCountMax=3'
|
|
183
|
-
];
|
|
184
|
-
|
|
185
|
-
if (privateKeyPath) {
|
|
186
|
-
sshArgs.push('-i', privateKeyPath, '-o', 'IdentitiesOnly=yes');
|
|
187
|
-
}
|
|
64
|
+
]);
|
|
188
65
|
|
|
189
66
|
sshArgs.push(`${username}@${hostname}`);
|
|
190
67
|
sshArgs.push('-t', 'tmux new-session -A -s SecureLink_Session 2>/dev/null || exec $SHELL -l');
|
|
@@ -213,66 +90,6 @@ async function connectSSH(username, hostname, privateKeyPath) {
|
|
|
213
90
|
}
|
|
214
91
|
}
|
|
215
92
|
|
|
216
|
-
async function promptLocalFileBrowser(startPath = process.cwd()) {
|
|
217
|
-
const items = fs.readdirSync(startPath, { withFileTypes: true });
|
|
218
|
-
|
|
219
|
-
const choices = [
|
|
220
|
-
{ name: '📁 .. (Up a directory)', value: 'UP' },
|
|
221
|
-
{ name: '✅ SELECT CURRENT DIRECTORY', value: 'SELECT_DIR' },
|
|
222
|
-
new inquirer.Separator(),
|
|
223
|
-
...items.map(item => ({
|
|
224
|
-
name: `${item.isDirectory() ? '📁' : '📄'} ${item.name}`,
|
|
225
|
-
value: path.join(startPath, item.name),
|
|
226
|
-
isDir: item.isDirectory()
|
|
227
|
-
}))
|
|
228
|
-
];
|
|
229
|
-
|
|
230
|
-
const { selection } = await inquirer.prompt([{
|
|
231
|
-
type: 'list',
|
|
232
|
-
name: 'selection',
|
|
233
|
-
message: `Browse local files [${startPath}]:`,
|
|
234
|
-
choices,
|
|
235
|
-
pageSize: 15
|
|
236
|
-
}]);
|
|
237
|
-
|
|
238
|
-
if (selection === 'UP') {
|
|
239
|
-
return promptLocalFileBrowser(path.dirname(startPath));
|
|
240
|
-
} else if (selection === 'SELECT_DIR') {
|
|
241
|
-
return startPath;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const stat = fs.statSync(selection);
|
|
245
|
-
if (stat.isDirectory()) {
|
|
246
|
-
const { action } = await inquirer.prompt([{
|
|
247
|
-
type: 'list',
|
|
248
|
-
name: 'action',
|
|
249
|
-
message: `Selected Folder: ${path.basename(selection)}`,
|
|
250
|
-
choices: [
|
|
251
|
-
{ name: '📂 Open Folder', value: 'open' },
|
|
252
|
-
{ name: '✅ Select this Folder for Transfer', value: 'select' }
|
|
253
|
-
]
|
|
254
|
-
}]);
|
|
255
|
-
|
|
256
|
-
if (action === 'open') return promptLocalFileBrowser(selection);
|
|
257
|
-
return selection;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return selection;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Calculate SHA-256 of a local file.
|
|
265
|
-
*/
|
|
266
|
-
async function calculateChecksum(filePath) {
|
|
267
|
-
return new Promise((resolve, reject) => {
|
|
268
|
-
const hash = crypto.createHash('sha256');
|
|
269
|
-
const stream = fs.createReadStream(filePath);
|
|
270
|
-
stream.on('error', err => resolve(null)); // likely a directory
|
|
271
|
-
stream.on('data', chunk => hash.update(chunk));
|
|
272
|
-
stream.on('end', () => resolve(hash.digest('hex')));
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
|
|
276
93
|
/**
|
|
277
94
|
* Perform an SCP file transfer through the Cloudflare tunnel.
|
|
278
95
|
*/
|
|
@@ -282,37 +99,16 @@ async function performSCP(username, hostname, direction, privateKeyPath) {
|
|
|
282
99
|
console.log(chalk.dim(' ─────────────────────────────────'));
|
|
283
100
|
|
|
284
101
|
let localPath;
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
{ name: '⌨️ Type path manually', value: 'manual' },
|
|
291
|
-
{ name: '🔍 Browse local files interactively', value: 'browse' }
|
|
292
|
-
]
|
|
293
|
-
}]);
|
|
294
|
-
|
|
295
|
-
if (pathMode === 'browse') {
|
|
296
|
-
localPath = await promptLocalFileBrowser();
|
|
102
|
+
let remotePath;
|
|
103
|
+
|
|
104
|
+
if (direction === 'upload') {
|
|
105
|
+
remotePath = await promptRemotePath(username, hostname, privateKeyPath, 'destination');
|
|
106
|
+
localPath = await promptLocalPath('client file/folder to upload');
|
|
297
107
|
} else {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
name: 'localPath',
|
|
301
|
-
message: `Local file/folder path:`,
|
|
302
|
-
validate: v => v.trim().length > 0 || 'Required',
|
|
303
|
-
}]);
|
|
304
|
-
localPath = ans.localPath.trim();
|
|
108
|
+
localPath = await promptLocalPath('client destination');
|
|
109
|
+
remotePath = await promptRemotePath(username, hostname, privateKeyPath, 'source');
|
|
305
110
|
}
|
|
306
111
|
|
|
307
|
-
const { remotePath } = await inquirer.prompt([
|
|
308
|
-
{
|
|
309
|
-
type: 'input',
|
|
310
|
-
name: 'remotePath',
|
|
311
|
-
message: `Remote path (relative to ${username}'s home or absolute):`,
|
|
312
|
-
validate: v => v.trim().length > 0 || 'Required',
|
|
313
|
-
}
|
|
314
|
-
]);
|
|
315
|
-
|
|
316
112
|
await showConnectionTrace('Local', 'Remote SCP');
|
|
317
113
|
|
|
318
114
|
const proxyCommand = `cloudflared access tcp --hostname ${hostname}`;
|
|
@@ -321,17 +117,21 @@ async function performSCP(username, hostname, direction, privateKeyPath) {
|
|
|
321
117
|
const scpArgs = [
|
|
322
118
|
'-r', // recursive just in case
|
|
323
119
|
'-o', `ProxyCommand=${proxyCommand}`,
|
|
324
|
-
'-o', 'StrictHostKeyChecking=accept-new'
|
|
120
|
+
'-o', 'StrictHostKeyChecking=accept-new',
|
|
121
|
+
'-o', 'IdentitiesOnly=yes',
|
|
122
|
+
...getSshControlOptions(hostname)
|
|
325
123
|
];
|
|
326
124
|
|
|
327
125
|
if (privateKeyPath) {
|
|
328
|
-
scpArgs.push('-i', privateKeyPath, '-o', '
|
|
126
|
+
scpArgs.push('-i', privateKeyPath, '-o', 'IdentityAgent=none');
|
|
329
127
|
}
|
|
330
128
|
|
|
129
|
+
const remoteSpec = `${username}@${hostname}:${formatScpRemotePath(remotePath)}`;
|
|
331
130
|
if (direction === 'upload') {
|
|
332
|
-
scpArgs.push(localPath,
|
|
131
|
+
scpArgs.push(localPath, remoteSpec);
|
|
333
132
|
} else {
|
|
334
|
-
|
|
133
|
+
// Download: remote source FIRST, then local destination
|
|
134
|
+
scpArgs.push(remoteSpec, localPath);
|
|
335
135
|
}
|
|
336
136
|
|
|
337
137
|
let localHash = null;
|
|
@@ -364,10 +164,12 @@ async function performSCP(username, hostname, direction, privateKeyPath) {
|
|
|
364
164
|
try {
|
|
365
165
|
const sshArgs = [
|
|
366
166
|
'-o', `ProxyCommand=${proxyCommand}`,
|
|
367
|
-
'-o', 'StrictHostKeyChecking=accept-new'
|
|
167
|
+
'-o', 'StrictHostKeyChecking=accept-new',
|
|
168
|
+
'-o', 'IdentitiesOnly=yes',
|
|
169
|
+
...getSshControlOptions(hostname)
|
|
368
170
|
];
|
|
369
|
-
if (privateKeyPath) sshArgs.push('-i', privateKeyPath, '-o', '
|
|
370
|
-
sshArgs.push(`${username}@${hostname}`, `shasum -a 256
|
|
171
|
+
if (privateKeyPath) sshArgs.push('-i', privateKeyPath, '-o', 'IdentityAgent=none');
|
|
172
|
+
sshArgs.push(`${username}@${hostname}`, `shasum -a 256 ${quoteRemoteShell(remotePath)} || sha256sum ${quoteRemoteShell(remotePath)}`);
|
|
371
173
|
|
|
372
174
|
const { stdout } = await execa('ssh', sshArgs, { reject: false });
|
|
373
175
|
const remoteHash = stdout.split(' ')[0].trim();
|
|
@@ -399,7 +201,7 @@ async function performSCP(username, hostname, direction, privateKeyPath) {
|
|
|
399
201
|
/**
|
|
400
202
|
* Main Client Mode entry point.
|
|
401
203
|
*/
|
|
402
|
-
export async function startClientMode() {
|
|
204
|
+
export async function startClientMode(options = {}) {
|
|
403
205
|
console.log('');
|
|
404
206
|
console.log(chalk.bold.cyan(' 🌐 CLIENT MODE — Access a Remote Machine'));
|
|
405
207
|
console.log(chalk.dim(' ──────────────────────────────────────────'));
|
|
@@ -462,6 +264,17 @@ export async function startClientMode() {
|
|
|
462
264
|
}
|
|
463
265
|
}
|
|
464
266
|
|
|
267
|
+
if (!targetUid && options.uid) {
|
|
268
|
+
targetUid = options.uid.trim();
|
|
269
|
+
const { password } = await inquirer.prompt([{
|
|
270
|
+
type: 'password',
|
|
271
|
+
name: 'password',
|
|
272
|
+
message: 'Enter the session password:',
|
|
273
|
+
validate: (v) => v.trim().length > 0 || 'Password is required to decrypt',
|
|
274
|
+
}]);
|
|
275
|
+
targetPassword = password.trim();
|
|
276
|
+
}
|
|
277
|
+
|
|
465
278
|
if (!targetUid) {
|
|
466
279
|
const answer = await inquirer.prompt([
|
|
467
280
|
{
|
|
@@ -486,7 +299,7 @@ export async function startClientMode() {
|
|
|
486
299
|
targetPassword = answer.password.trim();
|
|
487
300
|
}
|
|
488
301
|
|
|
489
|
-
const payload = await resolveUID(targetUid, targetPassword);
|
|
302
|
+
const payload = await resolveUID(BROKER_URL, targetUid, targetPassword);
|
|
490
303
|
if (!payload) {
|
|
491
304
|
process.exit(1);
|
|
492
305
|
}
|
|
@@ -550,7 +363,7 @@ export async function startClientMode() {
|
|
|
550
363
|
}
|
|
551
364
|
|
|
552
365
|
// Push secure telemetry to host
|
|
553
|
-
await pushTelemetry(targetUid, targetPassword, username);
|
|
366
|
+
await pushTelemetry(BROKER_URL, targetUid, targetPassword, username);
|
|
554
367
|
|
|
555
368
|
const { action } = await inquirer.prompt([
|
|
556
369
|
{
|
|
@@ -590,13 +403,15 @@ export async function startClientMode() {
|
|
|
590
403
|
if (reconnect) {
|
|
591
404
|
await handleSubsequentActions(username, hostname, privateKeyPath, targetUid, targetPassword);
|
|
592
405
|
}
|
|
406
|
+
|
|
407
|
+
await cleanupAll();
|
|
593
408
|
}
|
|
594
409
|
|
|
595
410
|
async function handleClientChat(uid, password, cachedChatUrl) {
|
|
596
411
|
let chatUrl = cachedChatUrl;
|
|
597
412
|
const spinner = createSpinner('Checking for active chat room...', networkSpinner).start();
|
|
598
413
|
|
|
599
|
-
const payload = await resolveUID(uid, password, true); // true = silent if possible, or just re-resolve
|
|
414
|
+
const payload = await resolveUID(BROKER_URL, uid, password, true); // true = silent if possible, or just re-resolve
|
|
600
415
|
if (payload && payload.chatUrl) {
|
|
601
416
|
chatUrl = payload.chatUrl;
|
|
602
417
|
}
|
|
@@ -680,21 +495,13 @@ async function performReverseForward(username, hostname, privateKeyPath) {
|
|
|
680
495
|
}
|
|
681
496
|
]);
|
|
682
497
|
|
|
683
|
-
const proxyCmd = `cloudflared access ssh --hostname ${hostname}`;
|
|
684
498
|
const portMap = `${remotePort}:localhost:${localPort}`;
|
|
685
499
|
|
|
686
|
-
const sshArgs = [
|
|
500
|
+
const sshArgs = buildSshArgs(hostname, privateKeyPath, [
|
|
687
501
|
'-N',
|
|
688
502
|
'-R', portMap,
|
|
689
|
-
'-o',
|
|
690
|
-
|
|
691
|
-
'-o', 'UserKnownHostsFile=/dev/null',
|
|
692
|
-
];
|
|
693
|
-
|
|
694
|
-
if (privateKeyPath) {
|
|
695
|
-
sshArgs.push('-i', privateKeyPath);
|
|
696
|
-
sshArgs.push('-o', 'IdentitiesOnly=yes');
|
|
697
|
-
}
|
|
503
|
+
'-o', 'ExitOnForwardFailure=yes',
|
|
504
|
+
]);
|
|
698
505
|
sshArgs.push(`${username}@${hostname}`);
|
|
699
506
|
|
|
700
507
|
console.log('');
|
|
@@ -712,4 +519,4 @@ async function performReverseForward(username, hostname, privateKeyPath) {
|
|
|
712
519
|
if (err.killed) return;
|
|
713
520
|
console.log(chalk.red(`\n ❌ Tunnel disconnected: ${err.message}`));
|
|
714
521
|
}
|
|
715
|
-
}
|
|
522
|
+
}
|