Skip to content

Commit 91b6458

Browse files
committed
Support LF as line ending for banner and identification string
Fixes #761
1 parent a620835 commit 91b6458

File tree

2 files changed

+133
-3
lines changed

2 files changed

+133
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
using Microsoft.VisualStudio.TestTools.UnitTesting;
2+
using Renci.SshNet.Common;
3+
using Renci.SshNet.Connection;
4+
using Renci.SshNet.Tests.Common;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Net;
9+
using System.Net.Sockets;
10+
using System.Text;
11+
12+
namespace Renci.SshNet.Tests.Classes.Connection
13+
{
14+
[TestClass]
15+
public class ProtocolVersionExchangeTest_ServerResponseValid_TerminatedByLineFeedWithoutCarriageReturn
16+
{
17+
private AsyncSocketListener _server;
18+
private ProtocolVersionExchange _protocolVersionExchange;
19+
private string _clientVersion;
20+
private TimeSpan _timeout;
21+
private IPEndPoint _serverEndPoint;
22+
private List<byte> _dataReceivedByServer;
23+
private byte[] _serverIdentification;
24+
private bool _clientDisconnected;
25+
private Socket _client;
26+
private SshIdentification _actual;
27+
28+
[TestInitialize]
29+
public void Setup()
30+
{
31+
Arrange();
32+
Act();
33+
}
34+
35+
[TestCleanup]
36+
public void Cleanup()
37+
{
38+
if (_server != null)
39+
{
40+
_server.Dispose();
41+
_server = null;
42+
}
43+
44+
if (_client != null)
45+
{
46+
_client.Shutdown(SocketShutdown.Both);
47+
_client.Close();
48+
_client = null;
49+
}
50+
}
51+
52+
protected void Arrange()
53+
{
54+
_clientVersion = "SSH-2.0-Renci.SshNet.SshClient.0.0.1";
55+
_timeout = TimeSpan.FromSeconds(5);
56+
_serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122);
57+
_dataReceivedByServer = new List<byte>();
58+
_serverIdentification = Encoding.UTF8.GetBytes("Welcome stranger!\n\nSSH-Zero-OurSSHAppliance\n\0");
59+
60+
_server = new AsyncSocketListener(_serverEndPoint);
61+
_server.Start();
62+
_server.BytesReceived += (bytes, socket) =>
63+
{
64+
_dataReceivedByServer.AddRange(bytes);
65+
socket.Send(_serverIdentification);
66+
socket.Shutdown(SocketShutdown.Send);
67+
};
68+
_server.Disconnected += (socket) => _clientDisconnected = true;
69+
70+
_client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
71+
_client.Connect(_serverEndPoint);
72+
73+
_protocolVersionExchange = new ProtocolVersionExchange();
74+
}
75+
76+
protected void Act()
77+
{
78+
_actual = _protocolVersionExchange.Start(_clientVersion, _client, _timeout);
79+
}
80+
81+
[TestMethod]
82+
public void StartShouldReturnIdentificationOfServer()
83+
{
84+
Assert.IsNotNull(_actual);
85+
Assert.AreEqual("Zero", _actual.ProtocolVersion);
86+
Assert.AreEqual("OurSSHAppliance", _actual.SoftwareVersion);
87+
Assert.IsNull(_actual.Comments);
88+
}
89+
90+
[TestMethod]
91+
public void ClientIdentificationWasSentToServer()
92+
{
93+
var expected = Encoding.UTF8.GetBytes(_clientVersion);
94+
95+
Assert.AreEqual(expected.Length + 2, _dataReceivedByServer.Count);
96+
97+
Assert.IsTrue(expected.SequenceEqual(_dataReceivedByServer.Take(expected.Length)));
98+
Assert.AreEqual(Session.CarriageReturn, _dataReceivedByServer[_dataReceivedByServer.Count - 2]);
99+
Assert.AreEqual(Session.LineFeed, _dataReceivedByServer[_dataReceivedByServer.Count - 1]);
100+
}
101+
102+
[TestMethod]
103+
public void ClientRemainsConnected()
104+
{
105+
Assert.IsTrue(_client.Connected);
106+
Assert.IsFalse(_clientDisconnected);
107+
}
108+
109+
[TestMethod]
110+
public void ClientDidNotReadPastIdentification()
111+
{
112+
var buffer = new byte[1];
113+
114+
var bytesReceived = _client.Receive(buffer);
115+
Assert.AreEqual(1, bytesReceived);
116+
Assert.AreEqual(0x00, buffer[0]);
117+
}
118+
}
119+
}

src/Renci.SshNet/Connection/ProtocolVersionExchange.cs

+14-3
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,21 @@ private static string SocketReadLine(Socket socket, TimeSpan timeout, List<byte>
133133
PacketDump.Create(buffer.ToArray(), 2)));
134134
}
135135

136-
if (byteRead == Session.LineFeed && buffer.Count > startPosition + 1 && buffer[buffer.Count - 2] == Session.CarriageReturn)
136+
if (byteRead == Session.LineFeed)
137137
{
138-
// Return current line without CRLF
139-
return Encoding.UTF8.GetString(buffer.ToArray(), startPosition, buffer.Count - (startPosition + 2));
138+
if (buffer.Count > startPosition + 1 && buffer[buffer.Count - 2] == Session.CarriageReturn)
139+
{
140+
// Return current line without CRLF
141+
return Encoding.UTF8.GetString(buffer.ToArray(), startPosition, buffer.Count - (startPosition + 2));
142+
}
143+
else
144+
{
145+
// Even though RFC4253 clearly indicates that the identification string should be terminated
146+
// by a CR LF we also support banners and identification strings that are terminated by a LF
147+
148+
// Return current line without LF
149+
return Encoding.UTF8.GetString(buffer.ToArray(), startPosition, buffer.Count - (startPosition + 1));
150+
}
140151
}
141152
}
142153

0 commit comments

Comments
 (0)