ClientAuthentication.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using Renci.SshNet.Common;
  5. namespace Renci.SshNet
  6. {
  7. internal class ClientAuthentication : IClientAuthentication
  8. {
  9. public void Authenticate(IConnectionInfoInternal connectionInfo, ISession session)
  10. {
  11. if (connectionInfo == null)
  12. throw new ArgumentNullException("connectionInfo");
  13. if (session == null)
  14. throw new ArgumentNullException("session");
  15. session.RegisterMessage("SSH_MSG_USERAUTH_FAILURE");
  16. session.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS");
  17. session.RegisterMessage("SSH_MSG_USERAUTH_BANNER");
  18. session.UserAuthenticationBannerReceived += connectionInfo.UserAuthenticationBannerReceived;
  19. try
  20. {
  21. // the exception to report an authentication failure with
  22. SshAuthenticationException authenticationException = null;
  23. // try to authenticate against none
  24. var noneAuthenticationMethod = connectionInfo.CreateNoneAuthenticationMethod();
  25. var authenticated = noneAuthenticationMethod.Authenticate(session);
  26. if (authenticated != AuthenticationResult.Success)
  27. {
  28. if (!TryAuthenticate(session, new AuthenticationState(connectionInfo.AuthenticationMethods), noneAuthenticationMethod.AllowedAuthentications.ToList(), ref authenticationException))
  29. {
  30. throw authenticationException;
  31. }
  32. }
  33. }
  34. finally
  35. {
  36. session.UserAuthenticationBannerReceived -= connectionInfo.UserAuthenticationBannerReceived;
  37. session.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE");
  38. session.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS");
  39. session.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER");
  40. }
  41. }
  42. private bool TryAuthenticate(ISession session,
  43. AuthenticationState authenticationState,
  44. ICollection<string> allowedAuthenticationMethods,
  45. ref SshAuthenticationException authenticationException)
  46. {
  47. if (allowedAuthenticationMethods.Count == 0)
  48. {
  49. authenticationException = new SshAuthenticationException("No authentication methods defined on SSH server.");
  50. return false;
  51. }
  52. // we want to try authentication methods in the order in which they were
  53. // passed in the ctor, not the order in which the SSH server returns
  54. // the allowed authentication methods
  55. var matchingAuthenticationMethods = authenticationState.SupportedAuthenticationMethods.Where(a => allowedAuthenticationMethods.Contains(a.Name)).ToList();
  56. if (matchingAuthenticationMethods.Count == 0)
  57. {
  58. authenticationException = new SshAuthenticationException(string.Format("No suitable authentication method found to complete authentication ({0}).", string.Join(",", allowedAuthenticationMethods.ToArray())));
  59. return false;
  60. }
  61. foreach (var authenticationMethod in GetOrderedAuthenticationMethods(authenticationState, matchingAuthenticationMethods))
  62. {
  63. if (authenticationState.FailedAuthenticationMethods.Contains(authenticationMethod))
  64. continue;
  65. // when the authentication method was previously executed, then skip the authentication
  66. // method as long as there's another authentication method to try; this is done to avoid
  67. // a stack overflow for servers that do not update the list of allowed authentication
  68. // methods after a partial success
  69. if (!authenticationState.ExecutedAuthenticationMethods.Contains(authenticationMethod))
  70. {
  71. // update state to reflect previosuly executed authentication methods
  72. authenticationState.ExecutedAuthenticationMethods.Add(authenticationMethod);
  73. }
  74. var authenticationResult = authenticationMethod.Authenticate(session);
  75. switch (authenticationResult)
  76. {
  77. case AuthenticationResult.PartialSuccess:
  78. if (TryAuthenticate(session, authenticationState, authenticationMethod.AllowedAuthentications, ref authenticationException))
  79. {
  80. authenticationResult = AuthenticationResult.Success;
  81. }
  82. break;
  83. case AuthenticationResult.Failure:
  84. authenticationState.FailedAuthenticationMethods.Add(authenticationMethod);
  85. authenticationException = new SshAuthenticationException(string.Format("Permission denied ({0}).", authenticationMethod.Name));
  86. break;
  87. case AuthenticationResult.Success:
  88. authenticationException = null;
  89. break;
  90. }
  91. if (authenticationResult == AuthenticationResult.Success)
  92. return true;
  93. }
  94. return false;
  95. }
  96. private IEnumerable<IAuthenticationMethod> GetOrderedAuthenticationMethods(AuthenticationState authenticationState, IEnumerable<IAuthenticationMethod> matchingAuthenticationMethods)
  97. {
  98. var skippedAuthenticationMethods = new List<IAuthenticationMethod>();
  99. foreach (var authenticationMethod in matchingAuthenticationMethods)
  100. {
  101. if (authenticationState.ExecutedAuthenticationMethods.Contains(authenticationMethod))
  102. {
  103. skippedAuthenticationMethods.Add(authenticationMethod);
  104. continue;
  105. }
  106. yield return authenticationMethod;
  107. }
  108. foreach (var authenticationMethod in skippedAuthenticationMethods)
  109. yield return authenticationMethod;
  110. }
  111. private class AuthenticationState
  112. {
  113. private readonly IList<IAuthenticationMethod> _supportedAuthenticationMethods;
  114. public AuthenticationState(IList<IAuthenticationMethod> supportedAuthenticationMethods)
  115. {
  116. _supportedAuthenticationMethods = supportedAuthenticationMethods;
  117. ExecutedAuthenticationMethods = new List<IAuthenticationMethod>();
  118. FailedAuthenticationMethods = new List<IAuthenticationMethod>();
  119. }
  120. /// <summary>
  121. /// Gets the list of authentication methods that were previously executed.
  122. /// </summary>
  123. /// <value>
  124. /// The list of authentication methods that were previously executed.
  125. /// </value>
  126. public IList<IAuthenticationMethod> ExecutedAuthenticationMethods { get; private set; }
  127. /// <summary>
  128. /// Gets the list of authentications methods that failed.
  129. /// </summary>
  130. /// <value>
  131. /// The list of authentications methods that failed.
  132. /// </value>
  133. public IList<IAuthenticationMethod> FailedAuthenticationMethods { get; private set; }
  134. /// <summary>
  135. /// Gets the list of supported authentication methods.
  136. /// </summary>
  137. /// <value>
  138. /// The list of supported authentication methods.
  139. /// </value>
  140. public IEnumerable<IAuthenticationMethod> SupportedAuthenticationMethods
  141. {
  142. get { return _supportedAuthenticationMethods; }
  143. }
  144. }
  145. }
  146. }