diff --git a/package-lock.json b/package-lock.json index fc25a326..8311cbb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,14 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@heroku/socksv5": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@heroku/socksv5/-/socksv5-0.0.9.tgz", + "integrity": "sha1-ejkFkhE2smZpeaD4a7TwYvZX95M=", + "requires": { + "ip-address": "^5.8.8" + } + }, "@types/mocha": { "version": "5.2.6", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.6.tgz", @@ -262,7 +270,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, "requires": { "tweetnacl": "^0.14.3" } @@ -1544,6 +1551,33 @@ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, + "ip-address": { + "version": "5.9.4", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-5.9.4.tgz", + "integrity": "sha512-dHkI3/YNJq4b/qQaz+c8LuarD3pY24JqZWfjB8aZx1gtpc2MDILu9L9jpZe1sHpzo/yWFweQVn+U//FhazUxmw==", + "requires": { + "jsbn": "1.1.0", + "lodash": "^4.17.15", + "sprintf-js": "1.1.2" + }, + "dependencies": { + "jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha1-sBMHyym2GKHtJux56RH4A8TaAEA=" + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + } + } + }, "is": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", @@ -3033,6 +3067,35 @@ "ssh2-streams": "~0.4.2" } }, + "ssh2-promise": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/ssh2-promise/-/ssh2-promise-0.1.7.tgz", + "integrity": "sha512-7iaFJQmTKekBzM/nbWZGaC472mqmgqSkz5qAlU8Hj4QIpTHgml/HCMKUX0mc7tOrqNlgwrginURoe+OG+PxnEg==", + "requires": { + "@heroku/socksv5": "^0.0.9", + "ssh2": "^0.8.9" + }, + "dependencies": { + "ssh2": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-0.8.9.tgz", + "integrity": "sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==", + "requires": { + "ssh2-streams": "~0.4.10" + } + }, + "ssh2-streams": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.4.10.tgz", + "integrity": "sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==", + "requires": { + "asn1": "~0.2.0", + "bcrypt-pbkdf": "^1.0.2", + "streamsearch": "~0.1.2" + } + } + } + }, "ssh2-streams": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.4.2.tgz", diff --git a/package.json b/package.json index e4ee3182..a76941ef 100644 --- a/package.json +++ b/package.json @@ -139,6 +139,24 @@ "description": "GDB commands to run when starting to debug", "default": [] }, + "proxyConnection": { + "type": "object", + "description": "If this is set then the extension will connect to an ssh host throw a proxy server and run GDB there", + "properties": { + "host": { + "type": "string", + "description": "Remote host name/ip to connect to" + }, + "username": { + "type": "string", + "description": "Username to connect as" + }, + "identity": { + "type": "string", + "description": "Absolute path to private key" + } + } + }, "ssh": { "required": [ "host", @@ -276,6 +294,27 @@ "description": "GDB commands to run when starting to debug", "default": [] }, + "proxyConnection": { + "type": "object", + "description": "This is the intermediate poly connection", + "properties": { + "host": { + "type": "string", + "description": "Poly host name/ip to connect to", + "default": "localhost" + }, + "username": { + "type": "string", + "description": "Username to connect as", + "default": "${4:my_user}" + }, + "identity": { + "type": "string", + "description": "Absolute path to private key", + "dafault": "${4:/home/my_user/.ssh/id_rsa}" + } + } + }, "ssh": { "required": [ "host", @@ -953,9 +992,10 @@ "postinstall": "node ./node_modules/vscode/bin/install" }, "dependencies": { + "ssh2": "^0.8.2", + "ssh2-promise": "^0.1.7", "vscode-debugadapter": "^1.16.0", - "vscode-debugprotocol": "^1.16.0", - "ssh2": "^0.8.2" + "vscode-debugprotocol": "^1.16.0" }, "devDependencies": { "@types/mocha": "^5.2.6", diff --git a/src/backend/backend.ts b/src/backend/backend.ts index 0f35d063..cf5aaddf 100644 --- a/src/backend/backend.ts +++ b/src/backend/backend.ts @@ -47,10 +47,14 @@ export interface SSHArguments { x11host: string; bootstrap: string; } - +export interface ProxySSHArguments { + host: string; + username: string; + identity: string; +} export interface IBackend { load(cwd: string, target: string, procArgs: string, separateConsole: string): Thenable; - ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string, attach: boolean): Thenable; + ssh(proxyConnection: ProxySSHArguments, args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string, attach: boolean): Thenable; attach(cwd: string, executable: string, target: string): Thenable; connect(cwd: string, executable: string, target: string): Thenable; start(): Thenable; diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index c6871848..8cb226c8 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -1,4 +1,4 @@ -import { Breakpoint, IBackend, Thread, Stack, SSHArguments, Variable, VariableObject, MIError } from "../backend"; +import { Breakpoint, IBackend, Thread, Stack, SSHArguments, ProxySSHArguments, Variable, VariableObject, MIError } from "../backend"; import * as ChildProcess from "child_process"; import { EventEmitter } from "events"; import { parseMI, MINode } from '../mi_parse'; @@ -9,6 +9,7 @@ import { posix } from "path"; import * as nativePath from "path"; const path = posix; import { Client } from "ssh2"; +import ssh2_promise = require('ssh2-promise'); export function escape(str: string) { return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\""); @@ -90,11 +91,17 @@ export class MI2 extends EventEmitter implements IBackend { }); } - ssh(args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string, attach: boolean): Thenable { + ssh(proxyConnection: ProxySSHArguments, args: SSHArguments, cwd: string, target: string, procArgs: string, separateConsole: string, attach: boolean): Thenable { return new Promise((resolve, reject) => { this.isSSH = true; this.sshReady = false; - this.sshConn = new Client(); + + if (proxyConnection != undefined) { + const remoteConnection = { host: args.host, username: args.user , password: args.password}; + this.sshConn = new ssh2_promise([proxyConnection, remoteConnection]); + } else { + this.sshConn = new Client(); + } if (separateConsole !== undefined) this.log("stderr", "WARNING: Output to terminal emulators are not supported over SSH"); @@ -134,50 +141,74 @@ export class MI2 extends EventEmitter implements IBackend { connectionArgs.password = args.password; } - this.sshConn.on("ready", () => { + //Promise + if (proxyConnection != undefined) { + this.sshConn.connect().then(() => { this.log("stdout", "Running " + this.application + " over ssh..."); const execArgs: any = {}; - if (args.forwardX11) { - execArgs.x11 = { - single: false, - screen: args.remotex11screen - }; - } + if (args.forwardX11) { execArgs.x11 = { single: false, screen: args.remotex11screen }; } let sshCMD = this.application + " " + this.preargs.join(" "); - if (args.bootstrap) sshCMD = args.bootstrap + " && " + sshCMD; - if (attach) - sshCMD += " -p " + target; + if (args.bootstrap) sshCMD = args.bootstrap + " && " + sshCMD; + if (attach) sshCMD += " -p " + target; this.sshConn.exec(sshCMD, execArgs, (err, stream) => { - if (err) { - this.log("stderr", "Could not run " + this.application + " over ssh!"); - this.log("stderr", err.toString()); - this.emit("quit"); - reject(); - return; - } + if (err) { this.log("stderr", "Could not run " + this.application + " over ssh!"); this.log("stderr", err.toString()); this.emit("quit"); reject(); return; } this.sshReady = true; this.stream = stream; stream.on("data", this.stdout.bind(this)); stream.stderr.on("data", this.stderr.bind(this)); - stream.on("exit", (() => { - this.emit("quit"); - this.sshConn.end(); - }).bind(this)); + stream.on("exit", (() => { this.emit("quit"); this.sshConn.end(); }).bind(this)); const promises = this.initCommands(target, cwd, true, attach); promises.push(this.sendCommand("environment-cd \"" + escape(cwd) + "\"")); - if (procArgs && procArgs.length && !attach) - promises.push(this.sendCommand("exec-arguments " + procArgs)); - Promise.all(promises).then(() => { - this.emit("debug-ready"); - resolve(); - }, reject); + if (procArgs && procArgs.length && !attach) promises.push(this.sendCommand("exec-arguments " + procArgs)); + Promise.all(promises).then(() => { this.emit("debug-ready"); resolve(); }, reject); }); - }).on("error", (err) => { - this.log("stderr", "Could not run " + this.application + " over ssh!"); - this.log("stderr", err.toString()); - this.emit("quit"); - reject(); - }).connect(connectionArgs); + }).catch(err => {this.log("stderr", "Could not run " + this.application + " over ssh!"); this.log("stderr", err.toString()); this.emit("quit"); reject(); }); + } else { + this.sshConn.on("ready", () => { + this.log("stdout", "Running " + this.application + " over ssh..."); + const execArgs: any = {}; + if (args.forwardX11) { + execArgs.x11 = { + single: false, + screen: args.remotex11screen + }; + } + let sshCMD = this.application + " " + this.preargs.join(" "); + if (args.bootstrap) sshCMD = args.bootstrap + " && " + sshCMD; + if (attach) + sshCMD += " -p " + target; + this.sshConn.exec(sshCMD, execArgs, (err, stream) => { + if (err) { + this.log("stderr", "Could not run " + this.application + " over ssh!"); + this.log("stderr", err.toString()); + this.emit("quit"); + reject(); + return; + } + this.sshReady = true; + this.stream = stream; + stream.on("data", this.stdout.bind(this)); + stream.stderr.on("data", this.stderr.bind(this)); + stream.on("exit", (() => { + this.emit("quit"); + this.sshConn.end(); + }).bind(this)); + const promises = this.initCommands(target, cwd, true, attach); + promises.push(this.sendCommand("environment-cd \"" + escape(cwd) + "\"")); + if (procArgs && procArgs.length && !attach) + promises.push(this.sendCommand("exec-arguments " + procArgs)); + Promise.all(promises).then(() => { + this.emit("debug-ready"); + resolve(); + }, reject); + }); + }).on("error", (err) => { + this.log("stderr", "Could not run " + this.application + " over ssh!"); + this.log("stderr", err.toString()); + this.emit("quit"); + reject(); + }).connect(connectionArgs); + } }); } @@ -197,7 +228,7 @@ export class MI2 extends EventEmitter implements IBackend { cmds.push(this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")); if (this.prettyPrint) cmds.push(this.sendCommand("enable-pretty-printing")); - for (let cmd of this.extraCommands) { + for (const cmd of this.extraCommands) { cmds.push(this.sendCommand(cmd)); } diff --git a/src/gdb.ts b/src/gdb.ts index c588801c..cf9abe76 100644 --- a/src/gdb.ts +++ b/src/gdb.ts @@ -2,7 +2,7 @@ import { MI2DebugSession } from './mibase'; import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; import { MI2 } from "./backend/mi2/mi2"; -import { SSHArguments, ValuesFormattingMode } from './backend/backend'; +import { SSHArguments, ProxySSHArguments, ValuesFormattingMode } from './backend/backend'; export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { cwd: string; @@ -15,6 +15,7 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum terminal: string; autorun: string[]; ssh: SSHArguments; + proxyConnection: ProxySSHArguments; valuesFormatting: ValuesFormattingMode; printCalls: boolean; showDevDebugOutput: boolean; @@ -31,6 +32,7 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum remote: boolean; autorun: string[]; ssh: SSHArguments; + proxyConnection: ProxySSHArguments; valuesFormatting: ValuesFormattingMode; printCalls: boolean; showDevDebugOutput: boolean; @@ -77,7 +79,7 @@ class GDBDebugSession extends MI2DebugSession { this.isSSH = true; this.trimCWD = args.cwd.replace(/\\/g, "/"); this.switchCWD = args.ssh.cwd; - this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, args.terminal, false).then(() => { + this.miDebugger.ssh(args.proxyConnection, args.ssh, args.ssh.cwd, args.target, args.arguments, args.terminal, false).then(() => { if (args.autorun) args.autorun.forEach(command => { this.miDebugger.sendUserInput(command); @@ -145,7 +147,7 @@ class GDBDebugSession extends MI2DebugSession { this.isSSH = true; this.trimCWD = args.cwd.replace(/\\/g, "/"); this.switchCWD = args.ssh.cwd; - this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, "", undefined, true).then(() => { + this.miDebugger.ssh(args.proxyConnection, args.ssh, args.ssh.cwd, args.target, "", undefined, true).then(() => { if (args.autorun) args.autorun.forEach(command => { this.miDebugger.sendUserInput(command); @@ -187,7 +189,7 @@ class GDBDebugSession extends MI2DebugSession { if (substitutions) { Object.keys(substitutions).forEach(source => { this.miDebugger.extraCommands.push("gdb-set substitute-path " + source + " " + substitutions[source]); - }) + }); } } } diff --git a/src/lldb.ts b/src/lldb.ts index 79bf5b0d..422ceee0 100644 --- a/src/lldb.ts +++ b/src/lldb.ts @@ -2,7 +2,7 @@ import { MI2DebugSession } from './mibase'; import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; import { MI2_LLDB } from "./backend/mi2/mi2lldb"; -import { SSHArguments, ValuesFormattingMode } from './backend/backend'; +import { SSHArguments, ProxySSHArguments, ValuesFormattingMode } from './backend/backend'; export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { cwd: string; @@ -13,6 +13,7 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum arguments: string; autorun: string[]; ssh: SSHArguments; + proxyConnection: ProxySSHArguments; valuesFormatting: ValuesFormattingMode; printCalls: boolean; showDevDebugOutput: boolean; @@ -69,7 +70,7 @@ class LLDBDebugSession extends MI2DebugSession { this.isSSH = true; this.trimCWD = args.cwd.replace(/\\/g, "/"); this.switchCWD = args.ssh.cwd; - this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, undefined, false).then(() => { + this.miDebugger.ssh(args.proxyConnection, args.ssh, args.ssh.cwd, args.target, args.arguments, undefined, false).then(() => { if (args.autorun) args.autorun.forEach(command => { this.miDebugger.sendUserInput(command); @@ -128,7 +129,7 @@ class LLDBDebugSession extends MI2DebugSession { if (substitutions) { Object.keys(substitutions).forEach(source => { this.miDebugger.extraCommands.push("settings set target.source-map " + source + " " + substitutions[source]); - }) + }); } } }