Skip to content

Commit 1ab1300

Browse files
committed
Handle unknown channel messages correctly
See discussion #1218 . Some servers send custom channel messages like 'keepalive@proftpd.org' as keep alive messages. This currently causes a NotSupportedException. According to the spec https://datatracker.ietf.org/doc/html/rfc4254#section-5.4 : "If the request is not recognized or is not supported for the channel, SSH_MSG_CHANNEL_FAILURE is returned." Send a failure message back instead of throwing an exception.
1 parent 3e6fc4f commit 1ab1300

File tree

2 files changed

+83
-3
lines changed

2 files changed

+83
-3
lines changed

src/Renci.SshNet/Channels/Channel.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Globalization;
32
using System.Net.Sockets;
43
using System.Threading;
54

@@ -715,8 +714,8 @@ private void OnChannelRequest(object sender, MessageEventArgs<ChannelRequestMess
715714
}
716715
else
717716
{
718-
// TODO: we should also send a SSH_MSG_CHANNEL_FAILURE message
719-
throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Request '{0}' is not supported.", e.Message.RequestName));
717+
var reply = new ChannelFailureMessage(LocalChannelNumber);
718+
SendMessage(reply);
720719
}
721720
}
722721
catch (Exception ex)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
using Microsoft.VisualStudio.TestTools.UnitTesting;
5+
6+
using Moq;
7+
8+
using Renci.SshNet.Common;
9+
using Renci.SshNet.Messages;
10+
using Renci.SshNet.Messages.Connection;
11+
12+
namespace Renci.SshNet.Tests.Classes.Channels
13+
{
14+
[TestClass]
15+
public class ChannelTest_OnSessionChannelRequestReceived_HandleUnknownMessage : ChannelTestBase
16+
{
17+
private uint _localWindowSize;
18+
private uint _localPacketSize;
19+
private uint _localChannelNumber;
20+
private ChannelStub _channel;
21+
private IList<ExceptionEventArgs> _channelExceptionRegister;
22+
private UnknownRequestInfo _requestInfo;
23+
24+
protected override void SetupData()
25+
{
26+
var random = new Random();
27+
28+
_localWindowSize = (uint) random.Next(1000, int.MaxValue);
29+
_localPacketSize = _localWindowSize - 1;
30+
_localChannelNumber = (uint) random.Next(0, int.MaxValue);
31+
_channelExceptionRegister = new List<ExceptionEventArgs>();
32+
_requestInfo = new UnknownRequestInfo();
33+
}
34+
35+
protected override void SetupMocks()
36+
{
37+
_ = SessionMock.Setup(p => p.ConnectionInfo)
38+
.Returns(new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "password")));
39+
_ = SessionMock.Setup(p => p.SendMessage(It.IsAny<Message>()));
40+
}
41+
42+
protected override void Arrange()
43+
{
44+
base.Arrange();
45+
46+
_channel = new ChannelStub(SessionMock.Object, _localChannelNumber, _localWindowSize, _localPacketSize);
47+
_channel.SetIsOpen(true);
48+
_channel.Exception += (sender, args) => _channelExceptionRegister.Add(args);
49+
}
50+
51+
protected override void Act()
52+
{
53+
SessionMock.Raise(s => s.ChannelRequestReceived += null,
54+
new MessageEventArgs<ChannelRequestMessage>(new ChannelRequestMessage(_localChannelNumber, _requestInfo)));
55+
}
56+
57+
[TestMethod]
58+
public void FailureMessageWasSent()
59+
{
60+
SessionMock.Verify(p => p.SendMessage(It.Is<ChannelFailureMessage>(m => m.LocalChannelNumber == _localChannelNumber)), Times.Once);
61+
}
62+
63+
[TestMethod]
64+
public void NoExceptionShouldHaveFired()
65+
{
66+
Assert.AreEqual(0, _channelExceptionRegister.Count);
67+
}
68+
}
69+
70+
internal class UnknownRequestInfo : RequestInfo
71+
{
72+
public override string RequestName
73+
{
74+
get
75+
{
76+
return nameof(UnknownRequestInfo);
77+
}
78+
}
79+
80+
}
81+
}

0 commit comments

Comments
 (0)