2
0

SshMessageFactory.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using Renci.SshNet.Common;
  5. using Renci.SshNet.Messages;
  6. using Renci.SshNet.Messages.Authentication;
  7. using Renci.SshNet.Messages.Connection;
  8. using Renci.SshNet.Messages.Transport;
  9. namespace Renci.SshNet
  10. {
  11. internal sealed class SshMessageFactory
  12. {
  13. private readonly MessageMetadata[] _enabledMessagesByNumber;
  14. private readonly bool[] _activatedMessagesById;
  15. private readonly object _lock = new object();
  16. internal static readonly MessageMetadata[] AllMessages = new MessageMetadata[]
  17. {
  18. new MessageMetadata<KeyExchangeInitMessage>(0, "SSH_MSG_KEXINIT", 20),
  19. new MessageMetadata<NewKeysMessage>(1, "SSH_MSG_NEWKEYS", 21),
  20. new MessageMetadata<RequestFailureMessage>(2, "SSH_MSG_REQUEST_FAILURE", 82),
  21. new MessageMetadata<ChannelOpenFailureMessage>(3, "SSH_MSG_CHANNEL_OPEN_FAILURE", 92),
  22. new MessageMetadata<ChannelFailureMessage>(4, "SSH_MSG_CHANNEL_FAILURE", 100),
  23. new MessageMetadata<ChannelExtendedDataMessage>(5, "SSH_MSG_CHANNEL_EXTENDED_DATA", 95),
  24. new MessageMetadata<ChannelDataMessage>(6, "SSH_MSG_CHANNEL_DATA", 94),
  25. new MessageMetadata<ChannelRequestMessage>(7, "SSH_MSG_CHANNEL_REQUEST", 98),
  26. new MessageMetadata<BannerMessage>(8, "SSH_MSG_USERAUTH_BANNER", 53),
  27. new MessageMetadata<InformationResponseMessage>(9, "SSH_MSG_USERAUTH_INFO_RESPONSE", 61),
  28. new MessageMetadata<FailureMessage>(10, "SSH_MSG_USERAUTH_FAILURE", 51),
  29. new MessageMetadata<DebugMessage>(11, "SSH_MSG_DEBUG", 4),
  30. new MessageMetadata<GlobalRequestMessage>(12, "SSH_MSG_GLOBAL_REQUEST", 80),
  31. new MessageMetadata<ChannelOpenMessage>(13, "SSH_MSG_CHANNEL_OPEN", 90),
  32. new MessageMetadata<ChannelOpenConfirmationMessage>(14, "SSH_MSG_CHANNEL_OPEN_CONFIRMATION", 91),
  33. new MessageMetadata<InformationRequestMessage>(15, "SSH_MSG_USERAUTH_INFO_REQUEST", 60),
  34. new MessageMetadata<UnimplementedMessage>(16, "SSH_MSG_UNIMPLEMENTED", 3),
  35. new MessageMetadata<RequestSuccessMessage>(17, "SSH_MSG_REQUEST_SUCCESS", 81),
  36. new MessageMetadata<ChannelSuccessMessage>(18, "SSH_MSG_CHANNEL_SUCCESS", 99),
  37. new MessageMetadata<PasswordChangeRequiredMessage>(19, "SSH_MSG_USERAUTH_PASSWD_CHANGEREQ", 60),
  38. new MessageMetadata<DisconnectMessage>(20, "SSH_MSG_DISCONNECT", 1),
  39. new MessageMetadata<SuccessMessage>(21, "SSH_MSG_USERAUTH_SUCCESS", 52),
  40. new MessageMetadata<PublicKeyMessage>(22, "SSH_MSG_USERAUTH_PK_OK", 60),
  41. new MessageMetadata<IgnoreMessage>(23, "SSH_MSG_IGNORE", 2),
  42. new MessageMetadata<ChannelWindowAdjustMessage>(24, "SSH_MSG_CHANNEL_WINDOW_ADJUST", 93),
  43. new MessageMetadata<ChannelEofMessage>(25, "SSH_MSG_CHANNEL_EOF", 96),
  44. new MessageMetadata<ChannelCloseMessage>(26, "SSH_MSG_CHANNEL_CLOSE", 97),
  45. new MessageMetadata<ServiceAcceptMessage>(27, "SSH_MSG_SERVICE_ACCEPT", 6),
  46. new MessageMetadata<KeyExchangeDhGroupExchangeGroup>(28, "SSH_MSG_KEX_DH_GEX_GROUP", 31),
  47. new MessageMetadata<KeyExchangeDhReplyMessage>(29, "SSH_MSG_KEXDH_REPLY", 31),
  48. new MessageMetadata<KeyExchangeDhGroupExchangeReply>(30, "SSH_MSG_KEX_DH_GEX_REPLY", 33),
  49. new MessageMetadata<KeyExchangeEcdhReplyMessage>(31, "SSH_MSG_KEX_ECDH_REPLY", 31)
  50. };
  51. private static readonly Dictionary<string, MessageMetadata> MessagesByName = CreateMessagesByNameMapping();
  52. /// <summary>
  53. /// Defines the highest message number that is currently supported.
  54. /// </summary>
  55. internal const byte HighestMessageNumber = 100;
  56. /// <summary>
  57. /// Defines the total number of supported messages.
  58. /// </summary>
  59. internal const int TotalMessageCount = 32;
  60. /// <summary>
  61. /// Initializes a new instance of the <see cref="SshMessageFactory"/> class.
  62. /// </summary>
  63. public SshMessageFactory()
  64. {
  65. _activatedMessagesById = new bool[TotalMessageCount];
  66. _enabledMessagesByNumber = new MessageMetadata[HighestMessageNumber + 1];
  67. }
  68. /// <summary>
  69. /// Disables and deactivate all messages.
  70. /// </summary>
  71. public void Reset()
  72. {
  73. Array.Clear(_activatedMessagesById, 0, _activatedMessagesById.Length);
  74. Array.Clear(_enabledMessagesByNumber, 0, _enabledMessagesByNumber.Length);
  75. }
  76. public Message Create(byte messageNumber)
  77. {
  78. if (messageNumber > HighestMessageNumber)
  79. {
  80. throw CreateMessageTypeNotSupportedException(messageNumber);
  81. }
  82. var enabledMessageMetadata = _enabledMessagesByNumber[messageNumber];
  83. if (enabledMessageMetadata is null)
  84. {
  85. MessageMetadata definedMessageMetadata = null;
  86. // find first message with specified number
  87. for (var i = 0; i < AllMessages.Length; i++)
  88. {
  89. var messageMetadata = AllMessages[i];
  90. if (messageMetadata.Number == messageNumber)
  91. {
  92. definedMessageMetadata = messageMetadata;
  93. break;
  94. }
  95. }
  96. if (definedMessageMetadata is null)
  97. {
  98. throw CreateMessageTypeNotSupportedException(messageNumber);
  99. }
  100. throw new SshException(string.Format(CultureInfo.InvariantCulture, "Message type {0} is not valid in the current context.", messageNumber));
  101. }
  102. return enabledMessageMetadata.Create();
  103. }
  104. /// <summary>
  105. /// Disables non-KeyExchange messages.
  106. /// </summary>
  107. /// <param name="strict">
  108. /// <see langword="true"/> to indicate the strict key exchange mode; otherwise <see langword="false"/>.
  109. /// <para>In strict key exchange mode, only below messages are allowed:</para>
  110. /// <list type="bullet">
  111. /// <item>SSH_MSG_KEXINIT -> 20</item>
  112. /// <item>SSH_MSG_NEWKEYS -> 21</item>
  113. /// <item>SSH_MSG_DISCONNECT -> 1</item>
  114. /// </list>
  115. /// <para>Note:</para>
  116. /// <para> The relevant KEX Reply MSG will be allowed from a sub class of KeyExchange class.</para>
  117. /// <para> For example, it calls <c>Session.RegisterMessage("SSH_MSG_KEX_ECDH_REPLY");</c> if the curve25519-sha256 KEX algorithm is selected per negotiation.</para>
  118. /// </param>
  119. public void DisableNonKeyExchangeMessages(bool strict)
  120. {
  121. for (var i = 0; i < AllMessages.Length; i++)
  122. {
  123. var messageMetadata = AllMessages[i];
  124. var messageNumber = messageMetadata.Number;
  125. if (strict)
  126. {
  127. if (messageNumber is not 20 and not 21 and not 1)
  128. {
  129. _enabledMessagesByNumber[messageNumber] = null;
  130. }
  131. }
  132. else
  133. {
  134. if (messageNumber is (> 2 and < 20) or > 30)
  135. {
  136. _enabledMessagesByNumber[messageNumber] = null;
  137. }
  138. }
  139. }
  140. }
  141. public void EnableActivatedMessages()
  142. {
  143. for (var i = 0; i < AllMessages.Length; i++)
  144. {
  145. var messageMetadata = AllMessages[i];
  146. if (!_activatedMessagesById[messageMetadata.Id])
  147. {
  148. continue;
  149. }
  150. var enabledMessageMetadata = _enabledMessagesByNumber[messageMetadata.Number];
  151. if (enabledMessageMetadata != null && enabledMessageMetadata != messageMetadata)
  152. {
  153. throw CreateMessageTypeAlreadyEnabledForOtherMessageException(messageMetadata.Number,
  154. messageMetadata.Name,
  155. enabledMessageMetadata.Name);
  156. }
  157. _enabledMessagesByNumber[messageMetadata.Number] = messageMetadata;
  158. }
  159. }
  160. public void EnableAndActivateMessage(string messageName)
  161. {
  162. if (messageName is null)
  163. {
  164. throw new ArgumentNullException(nameof(messageName));
  165. }
  166. lock (_lock)
  167. {
  168. if (!MessagesByName.TryGetValue(messageName, out var messageMetadata))
  169. {
  170. throw CreateMessageNotSupportedException(messageName);
  171. }
  172. var enabledMessageMetadata = _enabledMessagesByNumber[messageMetadata.Number];
  173. if (enabledMessageMetadata != null && enabledMessageMetadata != messageMetadata)
  174. {
  175. throw CreateMessageTypeAlreadyEnabledForOtherMessageException(messageMetadata.Number,
  176. messageMetadata.Name,
  177. enabledMessageMetadata.Name);
  178. }
  179. _enabledMessagesByNumber[messageMetadata.Number] = messageMetadata;
  180. _activatedMessagesById[messageMetadata.Id] = true;
  181. }
  182. }
  183. public void DisableAndDeactivateMessage(string messageName)
  184. {
  185. if (messageName is null)
  186. {
  187. throw new ArgumentNullException(nameof(messageName));
  188. }
  189. lock (_lock)
  190. {
  191. if (!MessagesByName.TryGetValue(messageName, out var messageMetadata))
  192. {
  193. throw CreateMessageNotSupportedException(messageName);
  194. }
  195. var enabledMessageMetadata = _enabledMessagesByNumber[messageMetadata.Number];
  196. if (enabledMessageMetadata != null && enabledMessageMetadata != messageMetadata)
  197. {
  198. throw CreateMessageTypeAlreadyEnabledForOtherMessageException(messageMetadata.Number,
  199. messageMetadata.Name,
  200. enabledMessageMetadata.Name);
  201. }
  202. _activatedMessagesById[messageMetadata.Id] = false;
  203. _enabledMessagesByNumber[messageMetadata.Number] = null;
  204. }
  205. }
  206. private static Dictionary<string, MessageMetadata> CreateMessagesByNameMapping()
  207. {
  208. var messagesByName = new Dictionary<string, MessageMetadata>(AllMessages.Length);
  209. for (var i = 0; i < AllMessages.Length; i++)
  210. {
  211. var messageMetadata = AllMessages[i];
  212. messagesByName.Add(messageMetadata.Name, messageMetadata);
  213. }
  214. return messagesByName;
  215. }
  216. private static SshException CreateMessageTypeNotSupportedException(byte messageNumber)
  217. {
  218. throw new SshException(string.Format(CultureInfo.InvariantCulture, "Message type {0} is not supported.", messageNumber));
  219. }
  220. private static SshException CreateMessageNotSupportedException(string messageName)
  221. {
  222. throw new SshException(string.Format(CultureInfo.InvariantCulture, "Message '{0}' is not supported.", messageName));
  223. }
  224. private static SshException CreateMessageTypeAlreadyEnabledForOtherMessageException(byte messageNumber, string messageName, string currentEnabledForMessageName)
  225. {
  226. throw new SshException(string.Format(CultureInfo.InvariantCulture,
  227. "Cannot enable message '{0}'. Message type {1} is already enabled for '{2}'.",
  228. messageName,
  229. messageNumber,
  230. currentEnabledForMessageName));
  231. }
  232. internal abstract class MessageMetadata
  233. {
  234. protected MessageMetadata(byte id, string name, byte number)
  235. {
  236. Id = id;
  237. Name = name;
  238. Number = number;
  239. }
  240. public byte Id { get; }
  241. public string Name { get; }
  242. public byte Number { get; }
  243. public abstract Message Create();
  244. }
  245. private sealed class MessageMetadata<T> : MessageMetadata
  246. where T : Message, new()
  247. {
  248. public MessageMetadata(byte id, string name, byte number)
  249. : base(id, name, number)
  250. {
  251. }
  252. public override Message Create()
  253. {
  254. return new T();
  255. }
  256. }
  257. }
  258. }