UNPKG

n8n-nodes-netdevices

Version:

n8n node to interact with network devices (Cisco, Juniper, Palo Alto PAN-OS, Ciena SAOS, Linux, etc.)

992 lines 42.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseConnection = void 0; exports.formatSSHPrivateKey = formatSSHPrivateKey; exports.validateSSHPrivateKey = validateSSHPrivateKey; const ssh2_1 = require("ssh2"); const events_1 = require("events"); let Logger; try { const { LoggerProxy } = require('n8n-workflow'); Logger = LoggerProxy; } catch (error) { Logger = { debug: console.log, info: console.log, warn: console.warn, error: console.error }; } function formatSSHPrivateKey(privateKey) { var _a, _b, _c; if (!privateKey) { throw new Error('Private key is required'); } let formattedKey = privateKey.trim(); formattedKey = formattedKey.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); formattedKey = formattedKey.trim(); if (formattedKey.includes('-----BEGIN') && formattedKey.includes('-----END')) { const lines = formattedKey.split('\n'); if (lines.length === 1) { if (formattedKey.includes('-----BEGIN RSA PRIVATE KEY-----')) { const beginMatch = formattedKey.match(/-----BEGIN RSA PRIVATE KEY-----(.*)-----END RSA PRIVATE KEY-----/); if (beginMatch) { const content = beginMatch[1].trim().replace(/\s/g, ''); const wrappedContent = ((_a = content.match(/.{1,64}/g)) === null || _a === void 0 ? void 0 : _a.join('\n')) || content; formattedKey = `-----BEGIN RSA PRIVATE KEY-----\n${wrappedContent}\n-----END RSA PRIVATE KEY-----`; } } else if (formattedKey.includes('-----BEGIN OPENSSH PRIVATE KEY-----')) { const beginMatch = formattedKey.match(/-----BEGIN OPENSSH PRIVATE KEY-----(.*)-----END OPENSSH PRIVATE KEY-----/); if (beginMatch) { const content = beginMatch[1].trim().replace(/\s/g, ''); const wrappedContent = ((_b = content.match(/.{1,64}/g)) === null || _b === void 0 ? void 0 : _b.join('\n')) || content; formattedKey = `-----BEGIN OPENSSH PRIVATE KEY-----\n${wrappedContent}\n-----END OPENSSH PRIVATE KEY-----`; } } else if (formattedKey.includes('-----BEGIN PRIVATE KEY-----')) { const beginMatch = formattedKey.match(/-----BEGIN PRIVATE KEY-----(.*)-----END PRIVATE KEY-----/); if (beginMatch) { const content = beginMatch[1].trim().replace(/\s/g, ''); const wrappedContent = ((_c = content.match(/.{1,64}/g)) === null || _c === void 0 ? void 0 : _c.join('\n')) || content; formattedKey = `-----BEGIN PRIVATE KEY-----\n${wrappedContent}\n-----END PRIVATE KEY-----`; } } } else { const beginIndex = lines.findIndex(line => line.includes('-----BEGIN')); const endIndex = lines.findIndex(line => line.includes('-----END')); if (beginIndex !== -1 && endIndex !== -1 && endIndex > beginIndex) { const keyLines = lines.slice(beginIndex, endIndex + 1); const cleanedLines = keyLines.map(line => { if (line.includes('-----BEGIN') || line.includes('-----END')) { return line.trim(); } else { return line.trim(); } }); formattedKey = cleanedLines.join('\n'); if (!formattedKey.endsWith('\n')) { formattedKey += '\n'; } } } return formattedKey; } if (!formattedKey.includes('-----BEGIN')) { if (formattedKey.length > 1000) { formattedKey = '-----BEGIN RSA PRIVATE KEY-----\n' + formattedKey + '\n-----END RSA PRIVATE KEY-----'; } else { formattedKey = '-----BEGIN OPENSSH PRIVATE KEY-----\n' + formattedKey + '\n-----END OPENSSH PRIVATE KEY-----'; } } return formattedKey; } function validateSSHPrivateKey(privateKey) { if (!privateKey) { throw new Error('Private key is required'); } const trimmedKey = privateKey.trim(); if (!trimmedKey.includes('-----BEGIN')) { throw new Error('Private key must start with -----BEGIN marker'); } const hasRsaEnd = trimmedKey.includes('-----END RSA PRIVATE KEY-----'); const hasPrivateKeyEnd = trimmedKey.includes('-----END PRIVATE KEY-----'); const hasOpenSshEnd = trimmedKey.includes('-----END OPENSSH PRIVATE KEY-----'); const hasEcEnd = trimmedKey.includes('-----END EC PRIVATE KEY-----'); const hasDsaEnd = trimmedKey.includes('-----END DSA PRIVATE KEY-----'); if (!(hasRsaEnd || hasPrivateKeyEnd || hasOpenSshEnd || hasEcEnd || hasDsaEnd)) { throw new Error('Private key must end with proper -----END marker'); } if (trimmedKey.length < 500) { throw new Error('Private key appears to be too short. Please ensure you have copied the complete key including BEGIN and END markers.'); } return true; } class BaseConnection extends events_1.EventEmitter { constructor(credentials, fastMode = false, connectionPooling = false, reuseConnection = false) { super(); this.isConnected = false; this.basePrompt = ''; this.enabledPrompt = ''; this.configPrompt = ''; this.timeout = 10000; this.encoding = 'utf8'; this.newline = '\n'; this.returnChar = '\r'; this.fastMode = false; this.commandTimeout = 8000; this.reuseConnection = false; this.connectionPooling = false; this.lastActivity = 0; this.lastSuccessfulAlgorithmIndex = 0; this.credentials = credentials; this.fastMode = fastMode; this.connectionPooling = connectionPooling; this.reuseConnection = reuseConnection; this.timeout = (credentials.timeout || 10) * 1000; this.commandTimeout = (credentials.commandTimeout || 10) * 1000; this.lastActivity = Date.now(); this.currentChannel = null; this.client = new ssh2_1.Client(); this.setupEventHandlers(); this.setupConnectionPooling(); } setupEventHandlers() { this.client.on('ready', () => { this.isConnected = true; this.emit('ready'); }); this.client.on('error', (error) => { this.isConnected = false; this.emit('error', error); }); this.client.on('end', () => { this.isConnected = false; this.emit('end'); }); this.client.on('close', () => { this.isConnected = false; this.emit('close'); }); } async connect() { Logger.debug('Starting SSH connection process', { host: this.credentials.host, port: this.credentials.port, username: this.credentials.username, authMethod: this.credentials.authMethod, deviceType: this.credentials.deviceType, timeout: (this.credentials.timeout || 10) * 1000, fastMode: this.fastMode, hasPrivateKey: !!this.credentials.privateKey, hasPassphrase: !!this.credentials.passphrase, hasPassword: !!this.credentials.password }); try { this.validateCredentials(); await this.tryConnect(); Logger.info('SSH connection established successfully', { host: this.credentials.host, port: this.credentials.port, username: this.credentials.username, authMethod: this.credentials.authMethod, algorithmIndex: this.lastSuccessfulAlgorithmIndex }); } catch (error) { Logger.error('SSH connection failed', { host: this.credentials.host, port: this.credentials.port, username: this.credentials.username, authMethod: this.credentials.authMethod, error: error instanceof Error ? error.message : String(error) }); throw error; } } validateCredentials() { Logger.debug('Validating credentials', { host: this.credentials.host, port: this.credentials.port, username: this.credentials.username, authMethod: this.credentials.authMethod }); if (!this.credentials.host) { throw new Error('Host is required for SSH connection'); } if (!this.credentials.username) { throw new Error('Username is required for SSH connection'); } if (this.credentials.authMethod === 'privateKey') { if (!this.credentials.privateKey) { throw new Error('SSH private key is required for private key authentication'); } try { validateSSHPrivateKey(this.credentials.privateKey); Logger.debug('Private key validation passed', { keyLength: this.credentials.privateKey.length, hasPassphrase: !!this.credentials.passphrase, passphraseLength: this.credentials.passphrase ? this.credentials.passphrase.length : 0 }); } catch (keyError) { Logger.error('Private key validation failed during credential validation', { error: keyError instanceof Error ? keyError.message : String(keyError), keyLength: this.credentials.privateKey.length, hasBeginMarker: this.credentials.privateKey.includes('-----BEGIN'), hasEndMarker: this.credentials.privateKey.includes('-----END') }); throw keyError; } } else { if (!this.credentials.password) { throw new Error('Password is required for password authentication'); } Logger.debug('Password authentication validation passed', { passwordLength: this.credentials.password.length }); } if (this.credentials.useJumpHost) { this.validateJumpHostConfig(); } } validateJumpHostConfig() { Logger.debug('Validating jump host config', { useJumpHost: this.credentials.useJumpHost, hasJumpHostHost: !!this.credentials.jumpHostHost, hasJumpHostUsername: !!this.credentials.jumpHostUsername, jumpHostAuthMethod: this.credentials.jumpHostAuthMethod }); if (!this.credentials.jumpHostHost) { throw new Error('Jump host hostname/IP is required when useJumpHost is enabled'); } if (!this.credentials.jumpHostPort) { throw new Error('Jump host port is required when useJumpHost is enabled'); } if (!this.credentials.jumpHostUsername) { throw new Error('Jump host username is required when useJumpHost is enabled'); } if (!this.credentials.jumpHostAuthMethod) { throw new Error('Jump host authentication method is required when useJumpHost is enabled'); } if (this.credentials.jumpHostAuthMethod === 'privateKey') { if (!this.credentials.jumpHostPrivateKey) { throw new Error('Jump host SSH private key is required for private key authentication'); } try { validateSSHPrivateKey(this.credentials.jumpHostPrivateKey); Logger.debug('Jump host private key validation passed', { keyLength: this.credentials.jumpHostPrivateKey.length, hasPassphrase: !!this.credentials.jumpHostPassphrase, passphraseLength: this.credentials.jumpHostPassphrase ? this.credentials.jumpHostPassphrase.length : 0 }); } catch (keyError) { Logger.error('Jump host private key validation failed during credential validation', { error: keyError instanceof Error ? keyError.message : String(keyError), keyLength: this.credentials.jumpHostPrivateKey.length, hasBeginMarker: this.credentials.jumpHostPrivateKey.includes('-----BEGIN'), hasEndMarker: this.credentials.jumpHostPrivateKey.includes('-----END') }); throw keyError; } } else { if (!this.credentials.jumpHostPassword) { throw new Error('Jump host password is required for password authentication'); } } } async tryConnect() { const algorithmConfigs = this.getOptimizedAlgorithms(); Logger.debug('Trying SSH connection with algorithm configurations', { algorithmCount: algorithmConfigs.length, authMethod: this.credentials.authMethod }); for (let i = 0; i < algorithmConfigs.length; i++) { try { Logger.debug(`Attempting connection with algorithm config ${i + 1}/${algorithmConfigs.length}`, { algorithms: algorithmConfigs[i] }); await this.tryConnectWithConfig(algorithmConfigs[i]); await this.sessionPreparationWithTimeout(); Logger.info('SSH connection established successfully', { host: this.credentials.host, port: this.credentials.port, username: this.credentials.username, authMethod: this.credentials.authMethod, algorithmIndex: i + 1 }); if (this.connectionPooling) { const connectionKey = this.getConnectionKey(); BaseConnection.connectionPool.set(connectionKey, this); Logger.debug('Added connection to pool', { connectionKey }); } this.lastSuccessfulAlgorithmIndex = i; return; } catch (error) { Logger.warn(`Connection attempt ${i + 1}/${algorithmConfigs.length} failed`, { error: error instanceof Error ? error.message : String(error), host: this.credentials.host, port: this.credentials.port, authMethod: this.credentials.authMethod }); if (i === algorithmConfigs.length - 1) { Logger.error('All connection attempts failed', { host: this.credentials.host, port: this.credentials.port, username: this.credentials.username, authMethod: this.credentials.authMethod, finalError: error instanceof Error ? error.message : String(error) }); throw error; } } } } async tryConnectWithConfig(algorithms) { return new Promise((resolve, reject) => { const connectionTimeout = this.fastMode ? Math.min(this.timeout, 8000) : this.timeout; Logger.debug('Preparing SSH connection configuration', { host: this.credentials.host, port: this.credentials.port, username: this.credentials.username, connectionTimeout, authMethod: this.credentials.authMethod, algorithms }); const connectConfig = { host: this.credentials.host, port: this.credentials.port, username: this.credentials.username, readyTimeout: connectionTimeout, keepaliveInterval: this.credentials.keepAlive ? (this.fastMode ? 60000 : 30000) : undefined, algorithms: algorithms, hostHash: 'md5', debug: process.env.SSH_DEBUG === 'true' ? (msg) => Logger.debug('SSH2 Debug: ' + msg) : undefined }; if (this.credentials.authMethod === 'privateKey') { if (!this.credentials.privateKey) { Logger.error('SSH private key is missing for private key authentication'); reject(new Error('SSH private key is required for private key authentication')); return; } try { validateSSHPrivateKey(this.credentials.privateKey); const normalizedKey = formatSSHPrivateKey(this.credentials.privateKey); Logger.debug('Private key validation and formatting successful', { originalLength: this.credentials.privateKey.length, formattedLength: normalizedKey.length, hasBeginMarker: normalizedKey.includes('-----BEGIN'), hasEndMarker: normalizedKey.includes('-----END') }); connectConfig.privateKey = normalizedKey; } catch (keyError) { Logger.error('Private key validation failed', { error: keyError instanceof Error ? keyError.message : String(keyError), keyLength: this.credentials.privateKey.length, hasBeginMarker: this.credentials.privateKey.includes('-----BEGIN'), hasEndMarker: this.credentials.privateKey.includes('-----END') }); reject(new Error(`SSH private key validation failed: ${keyError instanceof Error ? keyError.message : String(keyError)}`)); return; } if (this.credentials.passphrase && this.credentials.passphrase.trim() !== '') { connectConfig.passphrase = this.credentials.passphrase; Logger.debug('Using passphrase for private key', { passphraseLength: this.credentials.passphrase.length }); } else { Logger.debug('No passphrase provided for private key'); } connectConfig.tryKeyboard = false; Logger.debug('Configured SSH private key authentication', { keyLength: this.credentials.privateKey.length, hasPassphrase: !!connectConfig.passphrase, tryKeyboard: connectConfig.tryKeyboard }); } else { if (!this.credentials.password) { Logger.error('Password is missing for password authentication'); reject(new Error('Password is required for password authentication')); return; } connectConfig.password = this.credentials.password; Logger.debug('Configured SSH password authentication', { passwordLength: this.credentials.password.length }); } const timeoutId = setTimeout(() => { Logger.error('SSH connection timeout reached', { host: this.credentials.host, port: this.credentials.port, timeout: connectionTimeout, authMethod: this.credentials.authMethod }); this.client.removeAllListeners(); reject(new Error(`Connection timeout after ${connectionTimeout}ms`)); }, connectionTimeout); this.client.once('ready', () => { Logger.info('SSH connection ready', { host: this.credentials.host, port: this.credentials.port, username: this.credentials.username, authMethod: this.credentials.authMethod }); clearTimeout(timeoutId); this.lastActivity = Date.now(); this.isConnected = true; resolve(); }); this.client.once('error', (error) => { Logger.error('SSH connection error', { error: error.message, host: this.credentials.host, port: this.credentials.port, username: this.credentials.username, authMethod: this.credentials.authMethod, level: error.level || 'unknown' }); clearTimeout(timeoutId); let enhancedError = error; if (error.message.includes('All configured authentication methods failed')) { enhancedError = new Error(`Authentication failed: ${error.message}. Please check your ${this.credentials.authMethod === 'privateKey' ? 'SSH private key and passphrase' : 'password'}.`); } else if (error.message.includes('connect ECONNREFUSED')) { enhancedError = new Error(`Connection refused: Cannot connect to ${this.credentials.host}:${this.credentials.port}. Please check if SSH service is running and the host/port are correct.`); } else if (error.message.includes('connect ETIMEDOUT')) { enhancedError = new Error(`Connection timed out: Cannot reach ${this.credentials.host}:${this.credentials.port}. Please check network connectivity and firewall settings.`); } else if (error.message.includes('getaddrinfo ENOTFOUND')) { enhancedError = new Error(`Host not found: Cannot resolve hostname ${this.credentials.host}. Please check the hostname or IP address.`); } reject(enhancedError); }); this.client.once('keyboard-interactive', (name, instructions, instructionsLang, prompts, finish) => { Logger.debug('Received keyboard-interactive authentication request', { name, instructions, promptCount: prompts.length, authMethod: this.credentials.authMethod }); if (this.credentials.authMethod === 'password' && this.credentials.password) { Logger.debug('Responding to keyboard-interactive with password'); finish([this.credentials.password]); } else { Logger.warn('Rejecting keyboard-interactive authentication for key-based auth'); finish([]); } }); this.client.once('banner', (message) => { Logger.debug('Received SSH banner', { banner: message.trim(), host: this.credentials.host }); }); try { Logger.debug('Initiating SSH connection', { host: this.credentials.host, port: this.credentials.port, username: this.credentials.username }); this.client.connect(connectConfig); } catch (error) { Logger.error('Failed to initiate SSH connection', { error: error instanceof Error ? error.message : String(error), host: this.credentials.host, port: this.credentials.port }); clearTimeout(timeoutId); reject(error); } }); } async disconnect() { return new Promise((resolve) => { if (this.connectionPooling && this.isAlive()) { this.lastActivity = Date.now(); resolve(); return; } if (this.currentChannel) { this.currentChannel.end(); this.currentChannel = null; } if (this.client) { const disconnectTimeout = setTimeout(() => { this.isConnected = false; resolve(); }, this.fastMode ? 2000 : 5000); this.client.once('close', () => { clearTimeout(disconnectTimeout); this.isConnected = false; if (this.connectionPooling) { const connectionKey = this.getConnectionKey(); BaseConnection.connectionPool.delete(connectionKey); } resolve(); }); this.client.end(); } else { resolve(); } }); } async sessionPreparation() { await this.createShellChannel(); if (this.fastMode) { await this.setBasePrompt(); } else { await Promise.all([ this.setBasePrompt(), this.disablePaging(), this.setTerminalWidth(), ]); } } async createShellChannel() { return new Promise((resolve, reject) => { this.client.shell((err, channel) => { if (err) { reject(err); return; } this.currentChannel = channel; this.currentChannel.setEncoding(this.encoding); const waitTime = this.fastMode ? 200 : 500; setTimeout(() => { resolve(); }, waitTime); }); }); } async setBasePrompt() { await this.writeChannel(this.returnChar); const output = await this.readChannel(); const lines = output.trim().split('\n'); const lastLine = lines[lines.length - 1]; this.basePrompt = lastLine.replace(/[>#$%]\s*$/, ''); this.enabledPrompt = this.basePrompt + '#'; this.configPrompt = this.basePrompt + '(config)#'; } async disablePaging() { } async setTerminalWidth() { } async writeChannel(data) { return new Promise((resolve, reject) => { if (this.currentChannel && this.currentChannel.writable) { Logger.debug('writeChannel: Writing to channel', { data: data.replace('\n', '\\n').replace('\r', '\\r') }); this.currentChannel.write(data, this.encoding, (err) => { if (err) { Logger.error('writeChannel: Channel write error', { error: err.message, stack: err.stack }); reject(err); } else { Logger.debug('writeChannel: Channel write successful'); setTimeout(resolve, 50); } }); } else { const msg = 'writeChannel: Cannot write to channel, it is not writable or does not exist.'; Logger.error(msg, { isChannel: !!this.currentChannel, isWritable: this.currentChannel ? this.currentChannel.writable : false, isReadable: this.currentChannel ? this.currentChannel.readable : false, isDestroyed: this.currentChannel ? this.currentChannel.destroyed : false, }); reject(new Error(msg)); } }); } async readChannel(timeout = 3000) { return new Promise((resolve) => { let buffer = ''; let timeoutId; const onData = (data) => { buffer += data; }; const cleanup = () => { if (this.currentChannel) { this.currentChannel.removeListener('data', onData); } if (timeoutId) { global.clearTimeout(timeoutId); } }; timeoutId = global.setTimeout(() => { cleanup(); resolve(buffer); }, timeout); if (this.currentChannel) { this.currentChannel.on('data', onData); } else { cleanup(); resolve(''); } }); } async readUntilPrompt(expectedPrompt, timeout = 10000) { return new Promise((resolve, reject) => { let buffer = ''; let timeoutId; let debounceId = null; const actualTimeout = this.fastMode ? Math.min(timeout, 5000) : timeout; const promptPatterns = [ /^\S+[>#$]\s*$/, /^\S+\(config\)#\s*$/, /^\S+\(config-if\)#\s*$/, /^\[\S+@\S+\s+\S+\][#$]\s*$/, ]; const cleanup = () => { if (this.currentChannel) { this.currentChannel.removeListener('data', onData); this.currentChannel.removeListener('error', onError); } if (timeoutId) clearTimeout(timeoutId); if (debounceId) clearTimeout(debounceId); }; const onData = (data) => { buffer += data; if (debounceId) clearTimeout(debounceId); debounceId = setTimeout(() => { const lines = buffer.trim().split('\n'); const lastLine = lines[lines.length - 1].trim(); const isPrompt = promptPatterns.some(pattern => pattern.test(lastLine)); if (isPrompt) { cleanup(); resolve(buffer); } }, 150); }; const onError = (error) => { cleanup(); reject(error); }; timeoutId = setTimeout(() => { cleanup(); resolve(buffer); }, actualTimeout); if (this.currentChannel) { this.currentChannel.on('data', onData); this.currentChannel.on('error', onError); } else { cleanup(); reject(new Error('No active channel available')); } }); } async sendCommand(command) { try { if (!this.isConnected || !this.currentChannel) { throw new Error('Not connected to device'); } this.lastActivity = Date.now(); await this.writeChannel(command + this.newline); const timeout = this.fastMode ? Math.min(this.commandTimeout, 5000) : this.commandTimeout; const output = await this.readUntilPrompt(undefined, timeout); const cleanOutput = this.sanitizeOutput(output, command); if (this.fastMode && command.startsWith('show')) { return { command, output: cleanOutput, success: true }; } const errorPatterns = [ /invalid command/i, /command not found/i, /syntax error/i, /unknown command/i, /access denied/i, /permission denied/i, /authentication failed/i, /connection lost/i, /timeout/i, /error:/i, /failed/i ]; const hasError = errorPatterns.some(pattern => pattern.test(cleanOutput)); if (hasError) { return { command, output: cleanOutput, success: false, error: 'Command execution returned an error' }; } return { command, output: cleanOutput, success: true }; } catch (error) { return { command, output: '', success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } async sendConfig(configCommands) { try { if (!this.isConnected || !this.currentChannel) { throw new Error('Not connected to device'); } await this.enterConfigMode(); let allOutput = ''; let hasError = false; let errorMessage = ''; for (const command of configCommands) { try { await this.writeChannel(command + this.newline); const output = await this.readUntilPrompt(undefined, this.timeout); allOutput += output; const errorPatterns = [ /invalid command/i, /syntax error/i, /unknown command/i, /access denied/i, /permission denied/i, /error:/i, /failed/i, /incomplete command/i, /ambiguous command/i ]; if (errorPatterns.some(pattern => pattern.test(output))) { hasError = true; errorMessage = `Error in command: ${command}`; break; } } catch (cmdError) { hasError = true; errorMessage = `Failed to execute command: ${command} - ${cmdError}`; break; } } try { await this.exitConfigMode(); } catch (exitError) { hasError = true; errorMessage = errorMessage || `Failed to exit configuration mode: ${exitError}`; } const cleanOutput = this.sanitizeOutput(allOutput, configCommands.join('; ')); return { command: configCommands.join('; '), output: cleanOutput, success: !hasError, error: hasError ? errorMessage : undefined }; } catch (error) { try { await this.exitConfigMode(); } catch (exitError) { } return { command: configCommands.join('; '), output: '', success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } async enterConfigMode() { await this.writeChannel('configure terminal' + this.newline); await this.readUntilPrompt(); } async exitConfigMode() { await this.writeChannel('exit' + this.newline); await this.readUntilPrompt(); } sanitizeOutput(output, command) { const escapedCommand = command.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); let cleanOutput = output.replace(new RegExp(escapedCommand, 'g'), ''); const escapedBasePrompt = this.basePrompt.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const escapedEnabledPrompt = this.enabledPrompt.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const escapedConfigPrompt = this.configPrompt.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); cleanOutput = cleanOutput.replace(new RegExp(escapedBasePrompt + '[>#$%]', 'g'), ''); cleanOutput = cleanOutput.replace(new RegExp(escapedEnabledPrompt, 'g'), ''); cleanOutput = cleanOutput.replace(new RegExp(escapedConfigPrompt, 'g'), ''); cleanOutput = cleanOutput.replace(/^\s+|\s+$/g, ''); cleanOutput = cleanOutput.replace(/\r\n/g, '\n'); cleanOutput = cleanOutput.replace(/\r/g, '\n'); return cleanOutput; } async getCurrentConfig() { return await this.sendCommand('show configuration'); } async saveConfig() { return await this.sendCommand('save configuration'); } async rebootDevice() { return await this.sendCommand('reboot'); } isAlive() { return this.isConnected && this.currentChannel && !this.currentChannel.destroyed; } async healthCheck() { try { if (!this.isAlive()) { return false; } await this.writeChannel(this.returnChar); const response = await this.readChannel(5000); return response.length > 0; } catch (error) { return false; } } getDeviceType() { return this.credentials.deviceType; } getHost() { return this.credentials.host; } getConnectionInfo() { return { host: this.credentials.host, port: this.credentials.port, deviceType: this.credentials.deviceType, connected: this.isAlive() }; } setupConnectionPooling() { if (this.connectionPooling && !BaseConnection.poolCleanupInterval) { BaseConnection.poolCleanupInterval = setInterval(() => { this.cleanupConnectionPool(); }, 300000); } } cleanupConnectionPool() { const now = Date.now(); const maxIdleTime = 600000; for (const [key, connection] of BaseConnection.connectionPool.entries()) { if (now - connection.lastActivity > maxIdleTime) { connection.disconnect(); BaseConnection.connectionPool.delete(key); } } } getConnectionKey() { return `${this.credentials.host}:${this.credentials.port}:${this.credentials.username}`; } getOptimizedAlgorithms() { if (this.fastMode) { return [ { serverHostKey: ['ssh-rsa', 'ecdsa-sha2-nistp256'], cipher: ['aes128-ctr', 'aes128-cbc'], hmac: ['hmac-sha1'], kex: ['diffie-hellman-group14-sha1', 'ecdh-sha2-nistp256'] } ]; } else { const keyBasedAlgorithms = { serverHostKey: [ 'ssh-rsa', 'rsa-sha2-256', 'rsa-sha2-512', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519' ], cipher: [ 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-gcm@openssh.com', 'aes256-gcm@openssh.com', 'aes128-cbc', 'aes192-cbc' ], hmac: ['hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'], kex: [ 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group14-sha256', 'ecdh-sha2-nistp256', 'diffie-hellman-group14-sha1' ] }; const passwordBasedAlgorithms = { serverHostKey: [ 'ssh-rsa', 'rsa-sha2-256', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ssh-ed25519' ], cipher: [ 'aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-cbc', 'aes192-cbc' ], hmac: ['hmac-sha2-256', 'hmac-sha1'], kex: [ 'diffie-hellman-group14-sha256', 'ecdh-sha2-nistp256', 'diffie-hellman-group14-sha1' ] }; const primaryAlgorithms = this.credentials.authMethod === 'privateKey' ? keyBasedAlgorithms : passwordBasedAlgorithms; return [ primaryAlgorithms, { serverHostKey: ['ssh-rsa'], cipher: ['aes128-cbc'], hmac: ['hmac-sha1'], kex: ['diffie-hellman-group1-sha1'] } ]; } } static forceCleanupConnectionPool() { for (const [key, connection] of BaseConnection.connectionPool.entries()) { connection.disconnect(); BaseConnection.connectionPool.delete(key); } if (BaseConnection.poolCleanupInterval) { clearInterval(BaseConnection.poolCleanupInterval); BaseConnection.poolCleanupInterval = null; } } static getConnectionPoolStatus() { return { totalConnections: BaseConnection.connectionPool.size, connections: Array.from(BaseConnection.connectionPool.keys()) }; } async sessionPreparationWithTimeout() { const sessionTimeout = this.fastMode ? 5000 : 10000; Logger.debug('Starting session preparation with timeout', { timeout: sessionTimeout, fastMode: this.fastMode }); return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { Logger.error('Session preparation timeout', { timeout: sessionTimeout, host: this.credentials.host }); reject(new Error(`Session preparation timeout after ${sessionTimeout}ms`)); }, sessionTimeout); this.sessionPreparation() .then(() => { clearTimeout(timeoutId); Logger.debug('Session preparation completed successfully'); resolve(); }) .catch((error) => { clearTimeout(timeoutId); Logger.error('Session preparation failed', { error: error instanceof Error ? error.message : String(error) }); reject(error); }); }); } } exports.BaseConnection = BaseConnection; BaseConnection.connectionPool = new Map(); BaseConnection.poolCleanupInterval = null; //# sourceMappingURL=base-connection.js.map