Add Test_Execute_Infinite_Timeout test

Refactor SSH Message type mapping (based on name instead of concreate type)
Clean up some TODO items
This commit is contained in:
olegkap_cp
2010-12-28 03:46:45 +00:00
parent 8c8f4acc47
commit a1b4d08009
13 changed files with 145 additions and 115 deletions
@@ -53,6 +53,19 @@ namespace Renci.SshClient.Tests.SshClientTests
}
}
[TestMethod]
public void Test_Execute_Infinite_Timeout()
{
using (var client = new SshClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
client.Connect();
var cmd = client.CreateCommand("sleep 10s");
cmd.Execute();
client.Disconnect();
}
}
[TestMethod]
public void Test_MultipleThread_10000_MultipleSessions()
{
@@ -61,7 +61,6 @@ namespace Renci.SshClient
/// Gets supported authentication methods for this connection.
/// </summary>
public IDictionary<string, Type> AuthenticationMethods { get; private set; }
// TODO: Restore AuthenticationMethods property functionality and allow connection only using supported method
/// <summary>
/// Gets supported compression algorithms for this connection.
@@ -173,16 +172,16 @@ namespace Renci.SshClient
{"ssh-dss", typeof(CryptoPublicKeyDss)},
};
//this.SupportedAuthenticationMethods = new Dictionary<string, Type>()
//{
// {"none", typeof(UserAuthenticationNone)},
// {"publickey", typeof(UserAuthenticationPublicKey)},
// {"password", typeof(UserAuthenticationPassword)},
// {"keyboard-interactive", typeof(UserAuthenticationKeyboardInteractive)},
// //{"hostbased", typeof(...)},
// //{"gssapi-keyex", typeof(...)},
// //{"gssapi-with-mic", typeof(...)},
//};
this.AuthenticationMethods = new Dictionary<string, Type>()
{
{"none", typeof(ConnectionInfo)},
{"publickey", typeof(PrivateKeyConnectionInfo)},
{"password", typeof(PasswordConnectionInfo)},
{"keyboard-interactive", typeof(KeyboardInteractiveConnectionInfo)},
//{"hostbased", typeof(...)},
//{"gssapi-keyex", typeof(...)},
//{"gssapi-with-mic", typeof(...)},
};
this.CompressionAlgorithms = new Dictionary<string, Type>()
{
@@ -216,9 +215,9 @@ namespace Renci.SshClient
{
this.Session = session;
this.Session.RegisterMessage<FailureMessage>();
this.Session.RegisterMessage<SuccessMessage>();
this.Session.RegisterMessage<BannerMessage>();
this.Session.RegisterMessage("SSH_MSG_USERAUTH_FAILURE");
this.Session.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS");
this.Session.RegisterMessage("SSH_MSG_USERAUTH_BANNER");
this.Session.UserAuthenticationFailureReceived += Session_UserAuthenticationFailureReceived;
this.Session.UserAuthenticationSuccessReceived += Session_UserAuthenticationSuccessMessageReceived;
@@ -232,9 +231,9 @@ namespace Renci.SshClient
this.Session.UserAuthenticationBannerReceived -= Session_UserAuthenticationBannerMessageReceived;
this.Session.MessageReceived -= Session_MessageReceived;
this.Session.UnRegisterMessage<FailureMessage>();
this.Session.UnRegisterMessage<SuccessMessage>();
this.Session.UnRegisterMessage<BannerMessage>();
this.Session.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE");
this.Session.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS");
this.Session.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER");
return this.IsAuthenticated;
}
@@ -75,7 +75,6 @@ namespace Renci.SshClient
/// </summary>
public override void Stop()
{
// TODO: This check should be moved to base class
// If port not started you cant stop it
if (!this.IsStarted)
return;
@@ -26,9 +26,9 @@ namespace Renci.SshClient
if (this.IsStarted)
return;
this.Session.RegisterMessage<RequestFailureMessage>();
this.Session.RegisterMessage<RequestSuccessMessage>();
this.Session.RegisterMessage<ChannelOpenMessage>();
this.Session.RegisterMessage("SSH_MSG_REQUEST_FAILURE");
this.Session.RegisterMessage("SSH_MSG_REQUEST_SUCCESS");
this.Session.RegisterMessage("SSH_MSG_CHANNEL_OPEN");
this.Session.RequestSuccessReceived += Session_RequestSuccess;
this.Session.RequestFailureReceived += Session_RequestFailure;
@@ -62,13 +62,13 @@ namespace Renci.SshClient
/// </summary>
protected override void OnAuthenticate()
{
this.Session.RegisterMessage<InformationRequestMessage>();
this.Session.RegisterMessage("SSH_MSG_USERAUTH_INFO_REQUEST");
this.Session.SendMessage(new RequestMessageKeyboardInteractive(ServiceNames.Connection, this.Username));
this.WaitHandle(this._authenticationCompleted);
this.Session.UnRegisterMessage<InformationRequestMessage>();
this.Session.UnRegisterMessage("SSH_MSG_USERAUTH_INFO_REQUEST");
if (this._exception != null)
{
@@ -2,13 +2,15 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Renci.SshClient.Common;
namespace Renci.SshClient.Messages
{
/// <summary>
/// Indicates that a class represents SSH message. This class cannot be inherited.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public sealed class MessageAttribute : Attribute
{
/// <summary>
@@ -70,7 +70,7 @@ namespace Renci.SshClient
/// </summary>
protected override void OnAuthenticate()
{
this.Session.RegisterMessage<PasswordChangeRequiredMessage>();
this.Session.RegisterMessage("SSH_MSG_USERAUTH_PASSWD_CHANGEREQ");
this.SendMessage(new RequestMessagePassword(ServiceNames.Connection, this.Username, this.Password));
@@ -115,7 +115,7 @@ namespace Renci.SshClient
if (e.Message is PasswordChangeRequiredMessage)
{
this.Session.UnRegisterMessage<PasswordChangeRequiredMessage>();
this.Session.UnRegisterMessage("SSH_MSG_USERAUTH_PASSWD_CHANGEREQ");
var eventTask = Task.Factory.StartNew(() =>
{
@@ -68,7 +68,7 @@ namespace Renci.SshClient
if (this.KeyFiles == null)
return;
this.Session.RegisterMessage<PublicKeyMessage>();
this.Session.RegisterMessage("SSH_MSG_USERAUTH_PK_OK");
foreach (var keyFile in this.KeyFiles)
{
@@ -112,7 +112,7 @@ namespace Renci.SshClient
}
}
this.Session.UnRegisterMessage<PublicKeyMessage>();
this.Session.UnRegisterMessage("SSH_MSG_USERAUTH_PK_OK");
}
/// <summary>
@@ -55,7 +55,7 @@ namespace Renci.SshClient.Security
{
base.Start(session, message);
this.Session.RegisterMessage<KeyExchangeDhReplyMessage>();
this.Session.RegisterMessage("SSH_MSG_KEXDH_REPLY");
this.Session.MessageReceived += Session_MessageReceived;
@@ -88,7 +88,7 @@ namespace Renci.SshClient.Security
if (message != null)
{
// Unregister message once received
this.Session.UnRegisterMessage<KeyExchangeDhReplyMessage>();
this.Session.UnRegisterMessage("SSH_MSG_KEXDH_REPLY");
this.HandleServerDhReply(message.HostKey, message.F, message.Signature);
}
@@ -55,7 +55,7 @@ namespace Renci.SshClient.Security
{
base.Start(session, message);
this.Session.RegisterMessage<KeyExchangeDhReplyMessage>();
this.Session.RegisterMessage("SSH_MSG_KEXDH_REPLY");
this.Session.MessageReceived += Session_MessageReceived;
@@ -89,7 +89,7 @@ namespace Renci.SshClient.Security
if (message != null)
{
// Unregister message once received
this.Session.UnRegisterMessage<KeyExchangeDhReplyMessage>();
this.Session.UnRegisterMessage("SSH_MSG_KEXDH_REPLY");
this.HandleServerDhReply(message.HostKey, message.F, message.Signature);
}
@@ -61,8 +61,8 @@ namespace Renci.SshClient.Security
{
base.Start(session, message);
this.Session.RegisterMessage<KeyExchangeDhGroupExchangeGroup>();
this.Session.RegisterMessage<KeyExchangeDhGroupExchangeReply>();
this.Session.RegisterMessage("SSH_MSG_KEX_DH_GEX_GROUP");
this.Session.RegisterMessage("SSH_MSG_KEX_DH_GEX_REPLY");
this.Session.MessageReceived += Session_MessageReceived;
@@ -87,7 +87,7 @@ namespace Renci.SshClient.Security
if (groupMessage != null)
{
// Unregister message once received
this.Session.UnRegisterMessage<KeyExchangeDhGroupExchangeGroup>();
this.Session.UnRegisterMessage("SSH_MSG_KEX_DH_GEX_GROUP");
// 2. Receive SSH_MSG_KEX_DH_GEX_GROUP
this._prime = groupMessage.SafePrime;
@@ -104,7 +104,7 @@ namespace Renci.SshClient.Security
if (replyMessage != null)
{
// Unregister message once received
this.Session.UnRegisterMessage<KeyExchangeDhGroupExchangeReply>();
this.Session.UnRegisterMessage("SSH_MSG_KEX_DH_GEX_REPLY");
this.HandleServerDhReply(replyMessage.HostKey, replyMessage.F, replyMessage.Signature);
}
@@ -33,8 +33,8 @@ namespace Renci.SshClient.Security
{
base.Start(session, message);
this.Session.RegisterMessage<KeyExchangeDhGroupExchangeGroup>();
this.Session.RegisterMessage<KeyExchangeDhGroupExchangeReply>();
this.Session.RegisterMessage("SSH_MSG_KEX_DH_GEX_GROUP");
this.Session.RegisterMessage("SSH_MSG_KEX_DH_GEX_REPLY");
this.Session.MessageReceived += Session_MessageReceived;
@@ -108,7 +108,7 @@ namespace Renci.SshClient.Security
if (groupMessage != null)
{
// Unregister message once received
this.Session.UnRegisterMessage<KeyExchangeDhGroupExchangeGroup>();
this.Session.UnRegisterMessage("SSH_MSG_KEX_DH_GEX_GROUP");
// 2. Receive SSH_MSG_KEX_DH_GEX_GROUP
this._prime = groupMessage.SafePrime;
@@ -125,7 +125,7 @@ namespace Renci.SshClient.Security
if (replyMessage != null)
{
// Unregister message once received
this.Session.UnRegisterMessage<KeyExchangeDhGroupExchangeReply>();
this.Session.UnRegisterMessage("SSH_MSG_KEX_DH_GEX_REPLY");
this.HandleServerDhReply(replyMessage.HostKey, replyMessage.F, replyMessage.Signature);
}
+92 -75
View File
@@ -18,6 +18,7 @@ using Renci.SshClient.Messages.Authentication;
using Renci.SshClient.Messages.Connection;
using Renci.SshClient.Messages.Transport;
using Renci.SshClient.Security;
using System.Diagnostics;
namespace Renci.SshClient
{
@@ -40,6 +41,11 @@ namespace Renci.SshClient
private static Regex _serverVersionRe = new Regex("^SSH-(?<protoversion>[^-]+)-(?<softwareversion>.+)( SP.+)?$", RegexOptions.Compiled);
/// <summary>
/// Holds metada about session messages
/// </summary>
private IEnumerable<MessageMetadata> _messagesMetadata;
/// <summary>
/// Controls how many authentication attempts can take place at the same time.
/// </summary>
@@ -425,6 +431,17 @@ namespace Renci.SshClient
connectResult.AsyncWaitHandle.WaitOne(this.ConnectionInfo.Timeout);
// Build list of available messages while connecting
this._messagesMetadata = (from type in this.GetType().Assembly.GetTypes()
from messageAttribute in type.GetCustomAttributes(false).OfType<MessageAttribute>()
select new MessageMetadata
{
Name = messageAttribute.Name,
Number = messageAttribute.Number,
Enabled = false,
Type = type,
}).ToList();
this._socket.EndConnect(connectResult);
this._socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1);
@@ -475,13 +492,13 @@ namespace Renci.SshClient
this.Write(Encoding.ASCII.GetBytes(string.Format("{0}\x0D\x0A", this.ClientVersion)));
// Register Transport response messages
this.RegisterMessage<DisconnectMessage>();
this.RegisterMessage<IgnoreMessage>();
this.RegisterMessage<UnimplementedMessage>();
this.RegisterMessage<DebugMessage>();
this.RegisterMessage<ServiceAcceptMessage>();
this.RegisterMessage<KeyExchangeInitMessage>();
this.RegisterMessage<NewKeysMessage>();
this.RegisterMessage("SSH_MSG_DISCONNECT");
this.RegisterMessage("SSH_MSG_IGNORE");
this.RegisterMessage("SSH_MSG_UNIMPLEMENTED");
this.RegisterMessage("SSH_MSG_DEBUG");
this.RegisterMessage("SSH_MSG_SERVICE_ACCEPT");
this.RegisterMessage("SSH_MSG_KEXINIT");
this.RegisterMessage("SSH_MSG_NEWKEYS");
// Start incoming request listener
@@ -508,8 +525,8 @@ namespace Renci.SshClient
throw new SshException("Username is not specified.");
}
// In future, if more then one authentication methods are supported perform the check here.
// Authenticate using provided connection info object
this.ConnectionInfo.Authenticate(this);
this._isAuthenticated = this.ConnectionInfo.IsAuthenticated;
@@ -1018,19 +1035,21 @@ namespace Renci.SshClient
this._keyExchangeCompletedWaitHandle.Reset();
// Connection type messages are not allowed during key exchange phase
this.UnRegisterMessage<GlobalRequestMessage>();
this.UnRegisterMessage<RequestSuccessMessage>();
this.UnRegisterMessage<RequestFailureMessage>();
this.UnRegisterMessage<ChannelOpenConfirmationMessage>();
this.UnRegisterMessage<ChannelOpenFailureMessage>();
this.UnRegisterMessage<ChannelWindowAdjustMessage>();
this.UnRegisterMessage<ChannelExtendedDataMessage>();
this.UnRegisterMessage<ChannelRequestMessage>();
this.UnRegisterMessage<ChannelSuccessMessage>();
this.UnRegisterMessage<ChannelFailureMessage>();
this.UnRegisterMessage<ChannelDataMessage>();
this.UnRegisterMessage<ChannelEofMessage>();
this.UnRegisterMessage<ChannelCloseMessage>();
this.UnRegisterMessage("SSH_MSG_GLOBAL_REQUEST");
this.UnRegisterMessage("SSH_MSG_REQUEST_SUCCESS");
this.UnRegisterMessage("SSH_MSG_REQUEST_FAILURE");
this.UnRegisterMessage("SSH_MSG_CHANNEL_OPEN_CONFIRMATION");
this.UnRegisterMessage("SSH_MSG_CHANNEL_OPEN_FAILURE");
this.UnRegisterMessage("SSH_MSG_CHANNEL_WINDOW_ADJUST");
this.UnRegisterMessage("SSH_MSG_CHANNEL_EXTENDED_DATA");
this.UnRegisterMessage("SSH_MSG_CHANNEL_REQUEST");
this.UnRegisterMessage("SSH_MSG_CHANNEL_SUCCESS");
this.UnRegisterMessage("SSH_MSG_CHANNEL_FAILURE");
this.UnRegisterMessage("SSH_MSG_CHANNEL_DATA");
this.UnRegisterMessage("SSH_MSG_CHANNEL_EOF");
this.UnRegisterMessage("SSH_MSG_CHANNEL_CLOSE");
// TODO: Replace it with algorithm which disables all messages but relevant for key exchange
var keyExchangeAlgorithmName = (from c in this.ConnectionInfo.KeyExchangeAlgorithms.Keys
from s in message.KeyExchangeAlgorithms
@@ -1084,19 +1103,19 @@ namespace Renci.SshClient
this._serverDecompression = this._keyExchange.Decompressor;
// Register Connection messages
this.RegisterMessage<GlobalRequestMessage>();
this.RegisterMessage<RequestSuccessMessage>();
this.RegisterMessage<RequestFailureMessage>();
this.RegisterMessage<ChannelOpenConfirmationMessage>();
this.RegisterMessage<ChannelOpenFailureMessage>();
this.RegisterMessage<ChannelWindowAdjustMessage>();
this.RegisterMessage<ChannelExtendedDataMessage>();
this.RegisterMessage<ChannelRequestMessage>();
this.RegisterMessage<ChannelSuccessMessage>();
this.RegisterMessage<ChannelFailureMessage>();
this.RegisterMessage<ChannelDataMessage>();
this.RegisterMessage<ChannelEofMessage>();
this.RegisterMessage<ChannelCloseMessage>();
this.RegisterMessage("SSH_MSG_GLOBAL_REQUEST");
this.RegisterMessage("SSH_MSG_REQUEST_SUCCESS");
this.RegisterMessage("SSH_MSG_REQUEST_FAILURE");
this.RegisterMessage("SSH_MSG_CHANNEL_OPEN_CONFIRMATION");
this.RegisterMessage("SSH_MSG_CHANNEL_OPEN_FAILURE");
this.RegisterMessage("SSH_MSG_CHANNEL_WINDOW_ADJUST");
this.RegisterMessage("SSH_MSG_CHANNEL_EXTENDED_DATA");
this.RegisterMessage("SSH_MSG_CHANNEL_REQUEST");
this.RegisterMessage("SSH_MSG_CHANNEL_SUCCESS");
this.RegisterMessage("SSH_MSG_CHANNEL_FAILURE");
this.RegisterMessage("SSH_MSG_CHANNEL_DATA");
this.RegisterMessage("SSH_MSG_CHANNEL_EOF");
this.RegisterMessage("SSH_MSG_CHANNEL_CLOSE");
if (this.NewKeysReceived != null)
{
@@ -1417,45 +1436,32 @@ namespace Renci.SshClient
#region Message loading functions
private delegate T LoadFunc<out T>(IEnumerable<byte> data);
private IDictionary<byte, LoadFunc<Message>> _registeredMessageTypes = new Dictionary<byte, LoadFunc<Message>>();
/// <summary>
/// Registers the message type. This will allow message type to be recognized by and handled by the system.
/// Registers SSH Message with the session.
/// </summary>
/// <remarks>Some message types are not allowed during cirtain times or same code can be used for different type of message</remarks>
/// <typeparam name="T">Message type</typeparam>
public void RegisterMessage<T>() where T : Message, new()
/// <param name="messageName">Name of the message.</param>
public void RegisterMessage(string messageName)
{
var messageAttribute = typeof(T).GetCustomAttributes(typeof(MessageAttribute), true).SingleOrDefault() as MessageAttribute;
if (messageAttribute == null)
throw new SshException(string.Format("Type '{0}' is not a valid message type.", typeof(T).AssemblyQualifiedName));
lock (this._registeredMessageTypes)
lock (this._messagesMetadata)
{
if (this._registeredMessageTypes.ContainsKey(messageAttribute.Number))
{
this.UnRegisterMessage<T>();
}
this._registeredMessageTypes.Add(messageAttribute.Number, new LoadFunc<Message>(Message.Load<T>));
Parallel.ForEach(
from m in this._messagesMetadata where m.Name == messageName select m,
(item) => { item.Enabled = true; });
}
}
/// <summary>
/// Registers the message type. Message that is not registered will not be allowed to be handled by the system.
/// Removes SSH message from the session
/// </summary>
/// <typeparam name="T"></typeparam>
public void UnRegisterMessage<T>()
/// <param name="messageName">Name of the message.</param>
public void UnRegisterMessage(string messageName)
{
var messageAttribute = typeof(T).GetCustomAttributes(typeof(MessageAttribute), true).SingleOrDefault() as MessageAttribute;
if (messageAttribute == null)
throw new SshException(string.Format("Type '{0}' is not a valid message type.", typeof(T).AssemblyQualifiedName));
this._registeredMessageTypes.Remove(messageAttribute.Number);
lock (this._messagesMetadata)
{
Parallel.ForEach(
from m in this._messagesMetadata where m.Name == messageName select m,
(item) => { item.Enabled = false; });
}
}
/// <summary>
@@ -1467,23 +1473,22 @@ namespace Renci.SshClient
{
var messageType = data.FirstOrDefault();
lock (this._registeredMessageTypes)
{
if (this._registeredMessageTypes.ContainsKey(messageType))
{
return this._registeredMessageTypes[messageType](data);
}
else
{
throw new NotSupportedException(string.Format("Message type '{0}' is not registered.", messageType));
}
}
var messageMetadata = (from m in this._messagesMetadata where m.Number == messageType && m.Enabled == true select m).SingleOrDefault();
if (messageMetadata == null)
throw new SshException(string.Format("Message type {0} is not valid.", messageType));
var message = messageMetadata.Type.CreateInstance<Message>();
message.Load(data);
return message;
}
#endregion
/// <summary>
/// Listnets for incoming message from the server and handles them. This method run as a task on seperate thread.
/// Listens for incoming message from the server and handles them. This method run as a task on separate thread.
/// </summary>
private void MessageListener()
{
@@ -1626,5 +1631,17 @@ namespace Renci.SshClient
}
#endregion
private class MessageMetadata
{
public string Name { get; set; }
public byte Number { get; set; }
public bool Enabled { get; set; }
public Type Type { get; set; }
}
}
}