import * as plugins from './smartssh.plugins.js'; import * as helpers from './smartssh.classes.helpers.js'; import { SshKey } from './smartssh.classes.sshkey.js'; export type TSshStrictHostKeyChecking = boolean | 'accept-new'; export interface ISshConfigOptions { strictHostKeyChecking?: TSshStrictHostKeyChecking; } export interface ISshConfigHostBlock { host: string; values: Record; } export class SshConfig { private _sshKeyArray: SshKey[]; private _options: ISshConfigOptions; constructor(sshKeyArrayArg: SshKey[], optionsArg: ISshConfigOptions = {}) { this._sshKeyArray = sshKeyArrayArg; this._options = { ...optionsArg, strictHostKeyChecking: optionsArg.strictHostKeyChecking ?? true, }; } /** * stores a config file */ store(dirPathArg: string) { const resolvedDir = helpers.resolveSshDirPath(dirPathArg); helpers.ensureSshDirSync(resolvedDir); const configArray: configObject[] = []; for (const sshKey of this._sshKeyArray) { let configString = ''; if (sshKey.host) { helpers.assertSafeHost(sshKey.host); const identityFilePath = plugins.path.join(resolvedDir, sshKey.host); configString = 'Host ' + sshKey.host + '\n' + ' HostName ' + sshKey.host + '\n' + ' IdentityFile ' + helpers.quoteSshConfigValue(identityFilePath) + '\n'; if (this._options.strictHostKeyChecking !== undefined) { const strictHostKeyChecking = this._options.strictHostKeyChecking; configString += ' StrictHostKeyChecking ' + (strictHostKeyChecking === 'accept-new' ? 'accept-new' : strictHostKeyChecking ? 'yes' : 'no') + '\n'; } } configArray.push({ configString: configString, authorized: sshKey.authorized, sshKey: sshKey, }); } let configFile: string = ''; for (const config of configArray) { configFile = configFile + config.configString + '\n'; } plugins.fs.writeFileSync(plugins.path.join(resolvedDir, 'config'), configFile); plugins.fs.chmodSync(plugins.path.join(resolvedDir, 'config'), 0o600); } read(dirPathArg: string) { const configPath = plugins.path.join(helpers.resolveSshDirPath(dirPathArg), 'config'); return plugins.fs.readFileSync(configPath, 'utf8'); } parse(dirPathArg: string) { return SshConfig.parse(this.read(dirPathArg)); } static parse(configStringArg: string): ISshConfigHostBlock[] { const blocks: ISshConfigHostBlock[] = []; let currentBlock: ISshConfigHostBlock | undefined; for (const rawLine of configStringArg.split(/\r?\n/)) { const line = rawLine.trim(); if (!line || line.startsWith('#')) { continue; } const [keyword, ...valueParts] = line.split(/\s+/); const value = valueParts.join(' '); if (!keyword || !value) { continue; } if (keyword.toLowerCase() === 'host') { if (!helpers.isSafeHost(value)) { continue; } currentBlock = { host: value, values: {}, }; blocks.push(currentBlock); continue; } if (currentBlock) { currentBlock.values[keyword.toLowerCase()] = value; } } return blocks; } } export interface configObject { configString: string; authorized: boolean; sshKey: SshKey; }