using Renci.SshNet; using Renci.SshNet.Common; using McpSsh.Server.Ssh; namespace McpSsh.Server.Terminal; public interface ITerminalConnection : IDisposable { bool DataAvailable { get; } string ReadAvailable(); void Write(string input); } public interface ITerminalConnectionFactory { ITerminalConnection Create(TerminalStartRequest request); } public sealed class SshNetTerminalConnectionFactory : ITerminalConnectionFactory { private const int ShellBufferSize = 16 * 1024; private readonly ISshKeyResolver _keyResolver; public SshNetTerminalConnectionFactory(ISshKeyResolver keyResolver) { _keyResolver = keyResolver; } public ITerminalConnection Create(TerminalStartRequest request) { try { var keyPath = _keyResolver.ResolveKeyPath(request.KeyPath); var keyFile = string.IsNullOrEmpty(request.KeyPassphrase) ? new PrivateKeyFile(keyPath) : new PrivateKeyFile(keyPath, request.KeyPassphrase); var client = new SshClient(request.Host, request.Port, request.Username, keyFile); client.Connect(); var stream = client.CreateShellStream( "xterm-256color", (uint)request.Cols, (uint)request.Rows, width: 0, height: 0, bufferSize: ShellBufferSize); return new SshNetTerminalConnection(client, stream, keyFile); } catch (SshAuthenticationException ex) { throw new SshToolException("ssh_authentication_failed", "SSH key authentication failed. Check the username, key path, and key passphrase.", ex); } catch (SshException ex) { throw new SshToolException("ssh_error", ex.Message, ex); } } private sealed class SshNetTerminalConnection : ITerminalConnection { private readonly SshClient _client; private readonly ShellStream _stream; private readonly PrivateKeyFile _keyFile; public SshNetTerminalConnection(SshClient client, ShellStream stream, PrivateKeyFile keyFile) { _client = client; _stream = stream; _keyFile = keyFile; } public bool DataAvailable => _stream.DataAvailable; public string ReadAvailable() { return _stream.Read(); } public void Write(string input) { _stream.Write(input); } public void Dispose() { _stream.Dispose(); _client.Dispose(); _keyFile.Dispose(); } } }