IntegratedNTLMAuthenticationProvider.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Net;
  10. using System.Text;
  11. using System.Runtime.InteropServices;
  12. using Utilities;
  13. using SMBLibrary.Authentication.GSSAPI;
  14. using SMBLibrary.Authentication.NTLM;
  15. using Microsoft.Win32;
  16. namespace SMBLibrary.Win32.Security
  17. {
  18. public class IntegratedNTLMAuthenticationProvider : NTLMAuthenticationProviderBase
  19. {
  20. public class AuthContext
  21. {
  22. public SecHandle ServerContext;
  23. public string DomainName;
  24. public string UserName;
  25. public string WorkStation;
  26. public string OSVersion;
  27. public bool IsGuest;
  28. public AuthContext(SecHandle serverContext)
  29. {
  30. ServerContext = serverContext;
  31. }
  32. }
  33. public override NTStatus GetChallengeMessage(out object context, NegotiateMessage negotiateMessage, out ChallengeMessage challengeMessage)
  34. {
  35. byte[] negotiateMessageBytes = negotiateMessage.GetBytes();
  36. SecHandle serverContext;
  37. byte[] challengeMessageBytes;
  38. try
  39. {
  40. challengeMessageBytes = SSPIHelper.GetType2Message(negotiateMessageBytes, out serverContext);
  41. }
  42. catch (Exception)
  43. {
  44. context = null;
  45. challengeMessage = null;
  46. // We assume that the problem is not with our implementation.
  47. return NTStatus.SEC_E_INVALID_TOKEN;
  48. }
  49. context = new AuthContext(serverContext);
  50. challengeMessage = new ChallengeMessage(challengeMessageBytes);
  51. return NTStatus.SEC_I_CONTINUE_NEEDED;
  52. }
  53. /// <summary>
  54. /// Authenticate will return false when the password is correct in these cases:
  55. /// 1. The correct password is blank and 'limitblankpassworduse' is set to 1.
  56. /// 2. The user is listed in the "Deny access to this computer from the network" list.
  57. /// </summary>
  58. public override NTStatus Authenticate(object context, AuthenticateMessage message)
  59. {
  60. AuthContext authContext = context as AuthContext;
  61. if (authContext == null)
  62. {
  63. // There are two possible reasons for authContext to be null:
  64. // 1. We have a bug in our implementation, let's assume that's not the case,
  65. // according to [MS-SMB2] 3.3.5.5.1 we aren't allowed to return SEC_E_INVALID_HANDLE anyway.
  66. // 2. The client sent AuthenticateMessage without sending NegotiateMessage first,
  67. // in this case the correct response is SEC_E_INVALID_TOKEN.
  68. return NTStatus.SEC_E_INVALID_TOKEN;
  69. }
  70. authContext.DomainName = message.DomainName;
  71. authContext.UserName = message.UserName;
  72. authContext.WorkStation = message.WorkStation;
  73. if (message.Version != null)
  74. {
  75. authContext.OSVersion = message.Version.ToString();
  76. }
  77. if ((message.NegotiateFlags & NegotiateFlags.Anonymous) > 0 ||
  78. !IsUserExists(message.UserName))
  79. {
  80. if (this.EnableGuestLogin)
  81. {
  82. authContext.IsGuest = true;
  83. return NTStatus.STATUS_SUCCESS;
  84. }
  85. else
  86. {
  87. return NTStatus.STATUS_LOGON_FAILURE;
  88. }
  89. }
  90. byte[] messageBytes = message.GetBytes();
  91. bool success;
  92. try
  93. {
  94. success = SSPIHelper.AuthenticateType3Message(authContext.ServerContext, messageBytes);
  95. }
  96. catch (Exception)
  97. {
  98. // We assume that the problem is not with our implementation.
  99. return NTStatus.SEC_E_INVALID_TOKEN;
  100. }
  101. if (success)
  102. {
  103. return NTStatus.STATUS_SUCCESS;
  104. }
  105. else
  106. {
  107. Win32Error result = (Win32Error)Marshal.GetLastWin32Error();
  108. // Windows will permit fallback when these conditions are met:
  109. // 1. The guest user account is enabled.
  110. // 2. The guest user account does not have a password set.
  111. // 3. The specified account does not exist.
  112. // OR:
  113. // The password is correct but 'limitblankpassworduse' is set to 1 (logon over a network is disabled for accounts without a password).
  114. bool allowFallback = (result == Win32Error.ERROR_ACCOUNT_RESTRICTION);
  115. if (allowFallback && this.EnableGuestLogin)
  116. {
  117. authContext.IsGuest = true;
  118. return NTStatus.STATUS_SUCCESS;
  119. }
  120. else
  121. {
  122. return ToNTStatus(result);
  123. }
  124. }
  125. }
  126. public override bool DeleteSecurityContext(ref object context)
  127. {
  128. AuthContext authContext = context as AuthContext;
  129. if (authContext == null)
  130. {
  131. return false;
  132. }
  133. SecHandle handle = ((AuthContext)context).ServerContext;
  134. uint result = SSPIHelper.DeleteSecurityContext(ref handle);
  135. bool success = (result == 0); // SEC_E_OK
  136. if (success)
  137. {
  138. context = null;
  139. }
  140. return success;
  141. }
  142. public override object GetContextAttribute(object context, GSSAttributeName attributeName)
  143. {
  144. AuthContext authContext = context as AuthContext;
  145. if (authContext != null)
  146. {
  147. switch (attributeName)
  148. {
  149. case GSSAttributeName.AccessToken:
  150. return SSPIHelper.GetAccessToken(authContext.ServerContext);
  151. case GSSAttributeName.DomainName:
  152. return authContext.DomainName;
  153. case GSSAttributeName.IsGuest:
  154. return authContext.IsGuest;
  155. case GSSAttributeName.MachineName:
  156. return authContext.WorkStation;
  157. case GSSAttributeName.OSVersion:
  158. return authContext.OSVersion;
  159. case GSSAttributeName.SessionKey:
  160. return SSPIHelper.GetSessionKey(authContext.ServerContext);
  161. case GSSAttributeName.UserName:
  162. return authContext.UserName;
  163. }
  164. }
  165. return null;
  166. }
  167. /// <summary>
  168. /// We immitate Windows, Guest logins are disabled in any of these cases:
  169. /// 1. The Guest account is disabled.
  170. /// 2. The Guest account has password set.
  171. /// 3. The Guest account is listed in the "deny access to this computer from the network" list.
  172. /// </summary>
  173. private bool EnableGuestLogin
  174. {
  175. get
  176. {
  177. return LoginAPI.ValidateUserPassword("Guest", String.Empty, LogonType.Network);
  178. }
  179. }
  180. public static bool IsUserExists(string userName)
  181. {
  182. return NetworkAPI.IsUserExists(userName);
  183. }
  184. public static NTStatus ToNTStatus(Win32Error errorCode)
  185. {
  186. switch (errorCode)
  187. {
  188. case Win32Error.ERROR_NO_TOKEN:
  189. return NTStatus.SEC_E_INVALID_TOKEN;
  190. case Win32Error.ERROR_ACCOUNT_RESTRICTION:
  191. return NTStatus.STATUS_ACCOUNT_RESTRICTION;
  192. case Win32Error.ERROR_INVALID_LOGON_HOURS:
  193. return NTStatus.STATUS_INVALID_LOGON_HOURS;
  194. case Win32Error.ERROR_INVALID_WORKSTATION:
  195. return NTStatus.STATUS_INVALID_WORKSTATION;
  196. case Win32Error.ERROR_PASSWORD_EXPIRED:
  197. return NTStatus.STATUS_PASSWORD_EXPIRED;
  198. case Win32Error.ERROR_ACCOUNT_DISABLED:
  199. return NTStatus.STATUS_ACCOUNT_DISABLED;
  200. case Win32Error.ERROR_LOGON_TYPE_NOT_GRANTED:
  201. return NTStatus.STATUS_LOGON_TYPE_NOT_GRANTED;
  202. case Win32Error.ERROR_ACCOUNT_EXPIRED:
  203. return NTStatus.STATUS_ACCOUNT_EXPIRED;
  204. case Win32Error.ERROR_PASSWORD_MUST_CHANGE:
  205. return NTStatus.STATUS_PASSWORD_MUST_CHANGE;
  206. case Win32Error.ERROR_ACCOUNT_LOCKED_OUT:
  207. return NTStatus.STATUS_ACCOUNT_LOCKED_OUT;
  208. default:
  209. return NTStatus.STATUS_LOGON_FAILURE;
  210. }
  211. }
  212. }
  213. }