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..a8671b06 100644 --- a/package.json +++ b/package.json @@ -201,6 +201,24 @@ "bootstrap": { "type": "string", "description": "Content will be executed on the SSH host before the debugger call." + }, + "proxyPort": { + "type": "number", + "description": "Port of the proxy server to connect to; (by default port = display + 6000)", + "default": 22 + }, + "proxyHost": { + "type": "string", + "description": "Hostname/ip of the proxy server", + "default": "localhost" + }, + "proxyUser": { + "type": "string", + "description": "Username to connect as" + }, + "proxyPassword": { + "type": "string", + "description": "Plain text password (unsafe; if possible use keyfile instead)" } } } @@ -338,6 +356,24 @@ "bootstrap": { "type": "string", "description": "Content will be executed on the SSH host before the debugger call." + }, + "proxyPort": { + "type": "number", + "description": "Port of the proxy server to connect to; (by default port = display + 6000)", + "default": 22 + }, + "proxyHost": { + "type": "string", + "description": "Hostname/ip of the proxy server", + "default": "localhost" + }, + "proxyUser": { + "type": "string", + "description": "Username to connect as" + }, + "proxyPassword": { + "type": "string", + "description": "Plain text password (unsafe; if possible use keyfile instead)" } } } @@ -411,6 +447,27 @@ "valuesFormatting": "parseText" } }, + { + "label": "GDB: Launch over SSH throw a proxy server", + "description": "Remotely starts the program using gdb", + "body": { + "type": "gdb", + "request": "launch", + "name": "${6:Launch Program (SSH)}", + "target": "${1:./bin/executable}", + "cwd": "^\"\\${workspaceRoot}\"", + "ssh": { + "host": "${2:127.0.0.1}", + "cwd": "${3:/home/remote_user/project/}", + "keyfile": "${4:/home/my_user/.ssh/id_rsa}", + "user": "${5:remote_user}", + "proxyHost": "${7:proxy_host}", + "proxyUser": "${8:proxy_user}", + "proxyPassword": "${9:proxy_password}" + }, + "valuesFormatting": "parseText" + } + }, { "label": "GDB: Launch GUI over SSH with X11 forwarding", "description": "Remotely starts the program using gdb with X11 forwarding", @@ -623,6 +680,24 @@ "bootstrap": { "type": "string", "description": "Content will be executed on the SSH host before the debugger call." + }, + "proxyPort": { + "type": "number", + "description": "Port of the proxy server to connect to; (by default port = display + 6000)", + "default": 22 + }, + "proxyHost": { + "type": "string", + "description": "Hostname/ip of the proxy server", + "default": "localhost" + }, + "proxyUser": { + "type": "string", + "description": "Username to connect as" + }, + "proxyPassword": { + "type": "string", + "description": "Plain text password (unsafe; if possible use keyfile instead)" } } } @@ -953,9 +1028,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..5bd1eca7 100644 --- a/src/backend/backend.ts +++ b/src/backend/backend.ts @@ -46,6 +46,10 @@ export interface SSHArguments { x11port: number; x11host: string; bootstrap: string; + proxyPort: number; + proxyHost: string; + proxyUser: string; + proxyPassword: string; } export interface IBackend { diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index c6871848..bc9d47a1 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -8,7 +8,7 @@ import * as fs from "fs"; 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, "\\\""); @@ -94,37 +94,29 @@ export class MI2 extends EventEmitter implements IBackend { return new Promise((resolve, reject) => { this.isSSH = true; this.sshReady = false; - this.sshConn = new Client(); if (separateConsole !== undefined) this.log("stderr", "WARNING: Output to terminal emulators are not supported over SSH"); - if (args.forwardX11) { - this.sshConn.on("x11", (info, accept, reject) => { - const xserversock = new net.Socket(); - xserversock.on("error", (err) => { - this.log("stderr", "Could not connect to local X11 server! Did you enable it in your display manager?\n" + err); - }); - xserversock.on("connect", () => { - const xclientsock = accept(); - xclientsock.pipe(xserversock).pipe(xclientsock); - }); - xserversock.connect(args.x11port, args.x11host); - }); - } - const connectionArgs: any = { host: args.host, port: args.port, username: args.user }; + const proxyConnection: any = { + host: args.proxyHost, + port: args.proxyPort, + username: args.proxyUser + }; + if (args.useAgent) { connectionArgs.agent = process.env.SSH_AUTH_SOCK; } else if (args.keyfile) { - if (require("fs").existsSync(args.keyfile)) - connectionArgs.privateKey = require("fs").readFileSync(args.keyfile); - else { + if (require("fs").existsSync(args.keyfile)) { + connectionArgs.identity = args.keyfile; + proxyConnection.identity = args.keyfile; + } else { this.log("stderr", "SSH key file does not exist!"); this.emit("quit"); reject(); @@ -132,9 +124,30 @@ export class MI2 extends EventEmitter implements IBackend { } } else { connectionArgs.password = args.password; + proxyConnection.password = args.proxyPassword; } - this.sshConn.on("ready", () => { + if (proxyConnection.host !== undefined) { + this.sshConn = new ssh2_promise([proxyConnection, connectionArgs]); + } else { + this.sshConn = new ssh2_promise(connectionArgs); + } + + if (args.forwardX11) { + this.sshConn.on("x11", (info, accept, reject) => { + const xserversock = new net.Socket(); + xserversock.on("error", (err) => { + this.log("stderr", "Could not connect to local X11 server! Did you enable it in your display manager?\n" + err); + }); + xserversock.on("connect", () => { + const xclientsock = accept(); + xclientsock.pipe(xserversock).pipe(xclientsock); + }); + xserversock.connect(args.x11port, args.x11host); + }); + } + + this.sshConn.connect().then(() => { this.log("stdout", "Running " + this.application + " over ssh..."); const execArgs: any = {}; if (args.forwardX11) { @@ -172,12 +185,12 @@ export class MI2 extends EventEmitter implements IBackend { resolve(); }, reject); }); - }).on("error", (err) => { + }).catch("error", (err) => { this.log("stderr", "Could not run " + this.application + " over ssh!"); this.log("stderr", err.toString()); this.emit("quit"); reject(); - }).connect(connectionArgs); + }); }); }