|
|
|
@ -34,10 +34,10 @@ export async function calculateActualConfig(config: FileSystemConfig): Promise<F
|
|
|
|
|
if (port) config.port = Number(port);
|
|
|
|
|
config.agent = replaceVariables(config.agent);
|
|
|
|
|
config.privateKeyPath = replaceVariables(config.privateKeyPath);
|
|
|
|
|
Logging.info(`Calculating actual config for ${config.name}`);
|
|
|
|
|
Logging.info(`[${config.name}] Calculating actual config`);
|
|
|
|
|
if (config.putty) {
|
|
|
|
|
if (process.platform !== 'win32') {
|
|
|
|
|
Logging.warning(`\tConfigurating uses putty, but platform is ${process.platform}`);
|
|
|
|
|
Logging.warning(`[${config.name}] \tConfigurating uses putty, but platform is ${process.platform}`);
|
|
|
|
|
}
|
|
|
|
|
let nameOnly = true;
|
|
|
|
|
if (config.putty === true) {
|
|
|
|
@ -78,13 +78,13 @@ export async function calculateActualConfig(config: FileSystemConfig): Promise<F
|
|
|
|
|
default:
|
|
|
|
|
throw new Error(`The requested PuTTY session uses an unsupported proxy method`);
|
|
|
|
|
}
|
|
|
|
|
Logging.debug(`\tReading PuTTY configuration lead to the following configuration:\n${JSON.stringify(config, null, 4)}`);
|
|
|
|
|
Logging.debug(`[${config.name}] \tReading PuTTY configuration lead to the following configuration:\n${JSON.stringify(config, null, 4)}`);
|
|
|
|
|
}
|
|
|
|
|
if (config.privateKeyPath) {
|
|
|
|
|
try {
|
|
|
|
|
const key = await toPromise<Buffer>(cb => readFile(config.privateKeyPath!, cb));
|
|
|
|
|
config.privateKey = key;
|
|
|
|
|
Logging.debug(`\tRead private key from ${config.privateKeyPath}`);
|
|
|
|
|
Logging.debug(`[${config.name}] \tRead private key from ${config.privateKeyPath}`);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
throw new Error(`Error while reading the keyfile at:\n${config.privateKeyPath}`);
|
|
|
|
|
}
|
|
|
|
@ -122,21 +122,21 @@ export async function calculateActualConfig(config: FileSystemConfig): Promise<F
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Logging.debug(`\tFinal configuration:\n${JSON.stringify(Logging.censorConfig(config), null, 4)}`);
|
|
|
|
|
Logging.debug(`[${config.name}] \tFinal configuration:\n${JSON.stringify(Logging.censorConfig(config), null, 4)}`);
|
|
|
|
|
return config;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function createSocket(config: FileSystemConfig): Promise<NodeJS.ReadableStream | null> {
|
|
|
|
|
config = (await calculateActualConfig(config))!;
|
|
|
|
|
if (!config) return null;
|
|
|
|
|
Logging.info(`Creating socket for ${config.name}`);
|
|
|
|
|
Logging.info(`[${config.name}] Creating socket`);
|
|
|
|
|
if (config.hop) {
|
|
|
|
|
Logging.debug(`\tHopping through ${config.hop}`);
|
|
|
|
|
Logging.debug(`[${config.name}] \tHopping through ${config.hop}`);
|
|
|
|
|
const hop = loadConfigs().find(c => c.name === config.hop);
|
|
|
|
|
if (!hop) throw new Error(`A SSH FS configuration with the name '${config.hop}' doesn't exist`);
|
|
|
|
|
const ssh = await createSSH(hop);
|
|
|
|
|
if (!ssh) {
|
|
|
|
|
Logging.debug(`\tFailed in connecting to hop ${config.hop} for ${config.name}`);
|
|
|
|
|
Logging.debug(`[${config.name}] \tFailed in connecting to hop ${config.hop}`);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return new Promise<NodeJS.ReadableStream>((resolve, reject) => {
|
|
|
|
@ -164,7 +164,7 @@ export async function createSocket(config: FileSystemConfig): Promise<NodeJS.Rea
|
|
|
|
|
throw new Error(`Unknown proxy method`);
|
|
|
|
|
}
|
|
|
|
|
return new Promise<NodeJS.ReadableStream>((resolve, reject) => {
|
|
|
|
|
Logging.debug(`Connecting to ${config.host}:${config.port || 22}`);
|
|
|
|
|
Logging.debug(`[${config.name}] Connecting to ${config.host}:${config.port || 22}`);
|
|
|
|
|
const socket = new Socket();
|
|
|
|
|
socket.connect(config.port || 22, config.host, () => resolve(socket as NodeJS.ReadableStream));
|
|
|
|
|
socket.once('error', reject);
|
|
|
|
@ -181,7 +181,7 @@ export async function createSSH(config: FileSystemConfig, sock?: NodeJS.Readable
|
|
|
|
|
client.once('ready', () => resolve(client));
|
|
|
|
|
client.once('timeout', () => reject(new Error(`Socket timed out while connecting SSH FS '${config.name}'`)));
|
|
|
|
|
client.on('keyboard-interactive', (name, instructions, lang, prompts, finish) => {
|
|
|
|
|
Logging.debug(`Received keyboard-interactive request with prompts "${JSON.stringify(prompts)}"`);
|
|
|
|
|
Logging.debug(`[${config.name}] Received keyboard-interactive request with prompts "${JSON.stringify(prompts)}"`);
|
|
|
|
|
Promise.all<string>(prompts.map(prompt =>
|
|
|
|
|
vscode.window.showInputBox({
|
|
|
|
|
password: true, // prompt.echo was false for me while testing password prompting
|
|
|
|
@ -198,7 +198,7 @@ export async function createSSH(config: FileSystemConfig, sock?: NodeJS.Readable
|
|
|
|
|
reject(error);
|
|
|
|
|
});
|
|
|
|
|
try {
|
|
|
|
|
Logging.info(`Creating SSH session for ${config.name} over the opened socket`);
|
|
|
|
|
Logging.info(`[${config.name}] Creating SSH session over the opened socket`);
|
|
|
|
|
client.connect(Object.assign<ConnectConfig, ConnectConfig, ConnectConfig>(config, { sock }, DEFAULT_CONFIG));
|
|
|
|
|
} catch (e) {
|
|
|
|
|
reject(e);
|
|
|
|
@ -207,14 +207,14 @@ export async function createSSH(config: FileSystemConfig, sock?: NodeJS.Readable
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function startSudo(shell: ClientChannel, config: FileSystemConfig, user: string | boolean = true): Promise<void> {
|
|
|
|
|
Logging.debug(`Turning shell into a sudo shell for ${typeof user === 'string' ? user : 'default sudo user'}`);
|
|
|
|
|
Logging.debug(`[${config.name}] Turning shell into a sudo shell for ${typeof user === 'string' ? `'${user}'` : 'default sudo user'}`);
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
function stdout(data: Buffer | string) {
|
|
|
|
|
data = data.toString();
|
|
|
|
|
if (data.trim() === 'SUDO OK') {
|
|
|
|
|
return cleanup(), resolve();
|
|
|
|
|
} else {
|
|
|
|
|
Logging.debug(`Unexpected STDOUT: ${data}`);
|
|
|
|
|
Logging.debug(`[${config.name}] Unexpected STDOUT: ${data}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
async function stderr(data: Buffer | string) {
|
|
|
|
@ -265,15 +265,15 @@ export async function getSFTP(client: Client, config: FileSystemConfig): Promise
|
|
|
|
|
config = (await calculateActualConfig(config))!;
|
|
|
|
|
if (!config) throw new Error('Couldn\'t calculate the config');
|
|
|
|
|
if (config.sftpSudo && !config.sftpCommand) {
|
|
|
|
|
Logging.warning('sftpSudo is set without sftpCommand. Assuming /usr/lib/openssh/sftp-server');
|
|
|
|
|
Logging.warning(`[${config.name}] sftpSudo is set without sftpCommand. Assuming /usr/lib/openssh/sftp-server`);
|
|
|
|
|
config.sftpCommand = '/usr/lib/openssh/sftp-server';
|
|
|
|
|
}
|
|
|
|
|
if (!config.sftpCommand) {
|
|
|
|
|
Logging.info(`Creating SFTP session using standard sftp subsystem`);
|
|
|
|
|
Logging.info(`[${config.name}] Creating SFTP session using standard sftp subsystem`);
|
|
|
|
|
return toPromise<SFTPWrapper>(cb => client.sftp(cb));
|
|
|
|
|
}
|
|
|
|
|
let cmd = config.sftpCommand;
|
|
|
|
|
Logging.info(`Creating SFTP session for ${config.name} using specified command: ${cmd}`);
|
|
|
|
|
Logging.info(`[${config.name}] Creating SFTP session using specified command: ${cmd}`);
|
|
|
|
|
const shell = await toPromise<ClientChannel>(cb => client.shell(false, cb));
|
|
|
|
|
// shell.stdout.on('data', (d: string | Buffer) => Logging.debug(`[${config.name}][SFTP-STDOUT] ${d}`));
|
|
|
|
|
// shell.stderr.on('data', (d: string | Buffer) => Logging.debug(`[${config.name}][SFTP-STDERR] ${d}`));
|
|
|
|
@ -286,7 +286,7 @@ export async function getSFTP(client: Client, config: FileSystemConfig): Promise
|
|
|
|
|
config.sftpSudo = mat ? mat[1] : true;
|
|
|
|
|
// Now the tricky part of splitting the sudo and sftp command
|
|
|
|
|
config.sftpCommand = cmd = stripSudo(cmd);
|
|
|
|
|
Logging.warning(`Reformed sftpCommand due to sudo to: ${cmd}`);
|
|
|
|
|
Logging.warning(`[${config.name}] Reformed sftpCommand due to sudo to: ${cmd}`);
|
|
|
|
|
}
|
|
|
|
|
// If the user wants sudo, we'll first convert this shell into a sudo shell
|
|
|
|
|
if (config.sftpSudo) await startSudo(shell, config, config.sftpSudo);
|
|
|
|
|