Description
I'm getting an ArgumentOutOfRangeException
from within SshDataStream.ReadBytes
when I execute the following code:
using (var ssh = new SshClient("ip", 22, "usr", "pwd"))
{
ssh.Connect();
IDictionary<Renci.SshNet.Common.TerminalModes, uint> termkvp = new Dictionary<Renci.SshNet.Common.TerminalModes, uint>();
termkvp.Add(Renci.SshNet.Common.TerminalModes.ECHO, 53);
using (var stream = ssh.CreateShellStream("xterm", 80, 24, 800, 600, 1024, termkvp))
using (var sr = new StreamReader(stream))
{
// wait for command promt
var ln = ReadLine(sr, ">");
while ((ln == null) || !ln.EndsWith(">"))
ln = ReadLine(sr, ">");
// send enable
stream.WriteLine("enable");
// wait for password promt
ln = ReadLine(sr, "Password: ");
while ((ln == null) || !ln.Equals("Password: "))
ln = ReadLine(sr, "Password: ");
// send password
stream.WriteLine("enable-pwd");
// wait for command promt
ln = ReadLine(sr, "#");
while ((ln == null) || !ln.EndsWith("#"))
ln = ReadLine(sr, "#");
var commandPromt = ln;
}
ssh.Disconnect();
}
Background: This code is for connecting to a Cisco router. I need privileged access to the router so I send the "enable" command. The router then promts for a password. The router does not echo the password. And I think that's exactly what confuses SshDataStream.ReadBytes
. The problem occurs only after sending the (hidden) password to the router.
Here is the stack trace from the exception:
Renci.SshNet.dll!Renci.SshNet.Common.SshDataStream.ReadBytes(int length) Line 222
Renci.SshNet.dll!Renci.SshNet.Common.SshDataStream.ReadUInt32() Line 173
Renci.SshNet.dll!Renci.SshNet.Common.SshDataStream.ReadString(System.Text.Encoding encoding) Line 198
Renci.SshNet.dll!Renci.SshNet.Common.SshData.ReadString(System.Text.Encoding encoding) Line 267
Renci.SshNet.dll!Renci.SshNet.Common.SshData.ReadNamesList() Line 289
Renci.SshNet.dll!Renci.SshNet.Messages.Transport.KeyExchangeInitMessage.LoadData() Line 146
Renci.SshNet.dll!Renci.SshNet.Common.SshData.Load(byte[] value, int offset) Line 126
Renci.SshNet.dll!Renci.SshNet.Session.LoadMessage(byte[] data, int offset) Line 1642
Renci.SshNet.dll!Renci.SshNet.Session.ReceiveMessage() Line 954
Renci.SshNet.dll!Renci.SshNet.Session.MessageListener() Line 1791
Renci.SshNet.dll!Renci.SshNet.Abstractions.ThreadAbstraction.ExecuteThread.AnonymousMethod__0(object o) Line 32
mscorlib.dll!System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(object state)
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)
mscorlib.dll!System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()
mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
When I remove the check which causes the exception to be thrown
if (bytesRead < length)
throw new ArgumentOutOfRangeException("length");
from SshDataStream.ReadBytes
, everything works fine.
So I'm wondering: Am I doing something wrong or is this a bug in SSH.NET? Could you remove that check from SshDataStream.ReadBytes
? If not, is there something else you can do to not getting messed up by such a hidden password use-case?
Note that the ReadLine
method I'm using above is a custom one which basically reads from the underlying stream byte by byte. But the problem described above also occured when I experimented with ShellStream.Expect
or ShellStream.ReadLine
. Nevertheless, here is the code of my ReadLine
method, just for reference:
private static string ReadLine(StreamReader stream, params string[] acceptedLineEndings)
{
var ret = "";
var eol = false;
while (!eol)
{
var b = stream.Read();
if (b >= 0)
{
var c = (char)b;
if (c == '\r')
{
}
else if (c == '\n')
{
eol = true;
}
else
{
ret += (char)b;
}
}
else
{
// if no more data and received data ends with a promt char, we return as well
foreach (var le in acceptedLineEndings)
{
if (ret.EndsWith(le))
{
eol = true;
break;
}
}
}
}
return ret;
}