Improve flag system + auto-toggle "DF-GE" flag

feature/ssh-config
Kelvin Schoofs 4 years ago
parent 6d124f81f6
commit 218188ab3a

@ -342,27 +342,102 @@ vscode.workspace.onDidChangeConfiguration(async (e) => {
}); });
loadConfigs(); loadConfigs();
export type FlagValue = string | boolean | null;
export type FlagCombo = [value: FlagValue, origin: string];
export const DEFAULT_FLAGS: string[] = ['-DF-GE'];
let cachedFlags: Record<string, FlagCombo> = {};
function calculateFlags(): Record<string, FlagCombo> {
const flags: Record<string, FlagCombo> = {};
const config = vscode.workspace.getConfiguration('sshfs').inspect<string[]>('flags');
if (!config) throw new Error(`Could not inspect "sshfs.flags" config field`);
function parseList(list: string[] | undefined, origin: string) {
if (list === undefined) return;
if (!Array.isArray(list)) throw new Error(`Expected string array for flags, but got: ${list}`);
const scope: Record<string, FlagCombo> = {};
for (const flag of list) {
let name: string = flag;
let value: FlagValue = null;
const eq = flag.indexOf('=');
if (eq !== -1) {
name = flag.substring(0, eq);
value = flag.substring(eq + 1);
} else if (flag.startsWith('+')) {
name = flag.substring(1);
value = true;
} else if (flag.startsWith('-')) {
name = flag.substring(1);
value = false;
}
name = name.toLocaleLowerCase();
if (name in scope) continue;
scope[name] = [value, origin];
}
// Override if necessary (since workspace settings come after global settings)
// Per "location", we still ignore duplicate flag names
Object.assign(flags, scope);
}
parseList(DEFAULT_FLAGS, 'Built-in Default');
parseList(config.defaultValue, 'Default Settings');
// Electron v11 crashes for DiffieHellman GroupExchange, although it's fixed in 11.3.0
if ((process.versions as { electron?: string }).electron?.match(/^11\.(0|1|2)\./)) {
parseList(['+DF-GE'], 'Fix for issue #239')
}
parseList(config.globalValue, 'Global Settings');
parseList(config.workspaceValue, 'Workspace Settings');
parseList(config.workspaceFolderValue, 'WorkspaceFolder Settings');
Logging.info(`Calculated config flags: ${JSON.stringify(flags)}`);
return cachedFlags = flags;
}
vscode.workspace.onDidChangeConfiguration(event => {
if (event.affectsConfiguration('sshfs.flags')) calculateFlags();
});
calculateFlags();
/** Returns a cached version. Gets updated by ConfigurationChangeEvent events */
export function getFlags(): Record<string, FlagCombo> { return cachedFlags; }
/** /**
* Checks the `sshfs.flags` config (overridable by e.g. workspace settings). * Checks the `sshfs.flags` config (overridable by e.g. workspace settings).
* - Flags are case-insensitive * - Flag names are case-insensitive
* - If a flag appears twice, the first mention of it is used * - If a flag appears twice, the first mention of it is used
* - If a flag appears as "NAME", `null` is returned * - If a flag appears as "NAME", `null` is returned
* - If a flag appears as "FLAG=VALUE", `VALUE` is returned as a string * - If a flag appears as "FLAG=VALUE", `VALUE` is returned as a string
* - If a flag appears as `+FLAG` (and no `=`), `true` is returned (as a boolean)
* - If a flag appears as `-FLAG` (and no `=`), `false` is returned (as a boolean)
* - If a flag is missing, `undefined` is returned (different from `null`!) * - If a flag is missing, `undefined` is returned (different from `null`!)
*
* For `undefined`, an actual `undefined` is returned. For all other cases, a FlagCombo
* is returned, e.g. "NAME" returns `[null, "someOrigin"]` and `"+F"` returns `[true, "someOrigin"]`
* @param target The name of the flag to look for * @param target The name of the flag to look for
*/ */
export function getFlag(target: string): string | null | undefined { export function getFlag(target: string): FlagCombo | undefined {
target = target.toLowerCase(); return calculateFlags()[target.toLowerCase()];
const flags: string[] = vscode.workspace.getConfiguration('sshfs').get('flags') || []; }
for (const flag of flags) {
let name: string = flag; /**
let value: any = null; * Built on top of getFlag. Tries to convert the flag value to a boolean using these rules:
const eq = flag.indexOf('='); * - If the flag isn't present, `missingValue` is returned
if (eq !== -1) { * Although this probably means I'm using a flag that I never added to `DEFAULT_FLAGS`
name = flag.substring(0, eq); * - Booleans are kept
value = flag.substring(eq + 1); * - `null` is counted as `true` (means a flag like "NAME" was present without any value or prefix)
} * - Strings try to get converted in a case-insensitive way:
if (name.toLowerCase() === target) return value; * - `true/t/yes/y` becomes true
} * - `false/f/no/n` becomes false
return undefined; * - All other strings result in an error
* @param target The name of the flag to look for
* @param defaultValue The value to return when no flag with the given name is present
* @returns The matching FlagCombo or `[missingValue, 'missing']` instead
*/
export function getFlagBoolean(target: string, missingValue: boolean): FlagCombo {
const combo = getFlag(target);
if (!combo) return [missingValue, 'missing'];
const [value, reason] = combo;
if (value == null) return [true, reason];
if (typeof value === 'boolean') return combo;
const lower = value.toLowerCase();
if (lower === 'true' || lower === 't' || lower === 'yes' || lower === 'y') return [true, reason];
if (lower === 'false' || lower === 'f' || lower === 'no' || lower === 'n') return [false, reason];
throw new Error(`Could not convert '${value}' for flag '${target}' to a boolean!`);
} }

@ -4,7 +4,7 @@ import { userInfo } from 'os';
import { Client, ClientChannel, ConnectConfig, SFTPWrapper as SFTPWrapperReal } from 'ssh2'; import { Client, ClientChannel, ConnectConfig, SFTPWrapper as SFTPWrapperReal } from 'ssh2';
import { SFTPStream } from 'ssh2-streams'; import { SFTPStream } from 'ssh2-streams';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { getConfig, getFlag } from './config'; import { getConfig, getFlag, getFlagBoolean } from './config';
import type { FileSystemConfig } from './fileSystemConfig'; import type { FileSystemConfig } from './fileSystemConfig';
import { censorConfig, Logging } from './logging'; import { censorConfig, Logging } from './logging';
import type { PuttySession } from './putty'; import type { PuttySession } from './putty';
@ -259,8 +259,9 @@ export async function createSSH(config: FileSystemConfig, sock?: NodeJS.Readable
} }
// Unless the flag 'DF-GE' is specified, disable DiffieHellman groupex algorithms (issue #239) // Unless the flag 'DF-GE' is specified, disable DiffieHellman groupex algorithms (issue #239)
// Note: If the config already specifies a custom `algorithms.key`, ignore it (trust the user?) // Note: If the config already specifies a custom `algorithms.key`, ignore it (trust the user?)
if (getFlag('DF-GE') === undefined && !finalConfig.algorithms?.kex) { const [flagV, flagR] = getFlagBoolean('DF-GE', false);
logging.info('Flag "DF-GE" not specified, disabling DiffieHellman kex groupex algorithms'); if (flagV) {
logging.info(`Flag "DF-GE" enabled due to '${flagR}', disabling DiffieHellman kex groupex algorithms`);
let kex: string[] = require('ssh2-streams/lib/constants').ALGORITHMS.KEX; let kex: string[] = require('ssh2-streams/lib/constants').ALGORITHMS.KEX;
kex = kex.filter(algo => !algo.includes('diffie-hellman-group-exchange')); kex = kex.filter(algo => !algo.includes('diffie-hellman-group-exchange'));
logging.debug(`\tResulting algorithms.kex: ${kex}`); logging.debug(`\tResulting algorithms.kex: ${kex}`);

Loading…
Cancel
Save