diff --git a/SMBLibrary/Authentication/GSSAPI/Enums/GSSAttributeName.cs b/SMBLibrary/Authentication/GSSAPI/Enums/GSSAttributeName.cs new file mode 100644 index 0000000..4c476b3 --- /dev/null +++ b/SMBLibrary/Authentication/GSSAPI/Enums/GSSAttributeName.cs @@ -0,0 +1,18 @@ +using System; + +namespace SMBLibrary.Authentication.GSSAPI +{ + public enum GSSAttributeName + { + AccessToken, + IsAnonymous, + + /// + /// Permit access to this user via the guest user account if the normal authentication process fails. + /// + IsGuest, + MachineName, + SessionKey, + UserName, + } +} diff --git a/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs b/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs new file mode 100644 index 0000000..d0ef096 --- /dev/null +++ b/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs @@ -0,0 +1,236 @@ +/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. + * + * You can redistribute this program and/or modify it under the terms of + * the GNU Lesser Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + */ +using System; +using System.Collections.Generic; +using SMBLibrary.Authentication.GSSAPI; +using Utilities; + +namespace SMBLibrary.Authentication.NTLM +{ + public delegate string GetUserPassword(string userName); + + public class IndependentNTLMAuthenticationProvider : NTLMAuthenticationProviderBase + { + public class AuthContext + { + public string WorkStation; + public byte[] ServerChallenge; + public string UserName; + public byte[] SessionKey; + public bool IsGuest; + + public AuthContext(string workStation, byte[] serverChallenge) + { + WorkStation = workStation; + ServerChallenge = serverChallenge; + } + } + + private GetUserPassword m_GetUserPassword; + + /// + /// The NTLM challenge response will be compared against the provided password. + /// + public IndependentNTLMAuthenticationProvider(GetUserPassword getUserPassword) + { + m_GetUserPassword = getUserPassword; + } + + public override Win32Error GetChallengeMessage(out object context, NegotiateMessage negotiateMessage, out ChallengeMessage challengeMessage) + { + byte[] serverChallenge = GenerateServerChallenge(); + context = new AuthContext(negotiateMessage.Workstation, serverChallenge); + + challengeMessage = new ChallengeMessage(); + challengeMessage.NegotiateFlags = NegotiateFlags.UnicodeEncoding | + NegotiateFlags.TargetNameSupplied | + NegotiateFlags.NTLMKey | + NegotiateFlags.TargetTypeServer | + NegotiateFlags.ExtendedSecurity | + NegotiateFlags.TargetInfo | + NegotiateFlags.Version; + if ((negotiateMessage.NegotiateFlags & NegotiateFlags.Sign) > 0) + { + // [MS-NLMP] If the client sends NTLMSSP_NEGOTIATE_SIGN to the server in the NEGOTIATE_MESSAGE, + // the server MUST return NTLMSSP_NEGOTIATE_SIGN to the client in the CHALLENGE_MESSAGE. + challengeMessage.NegotiateFlags |= NegotiateFlags.Sign; + } + if ((negotiateMessage.NegotiateFlags & NegotiateFlags.Use56BitEncryption) > 0) + { + challengeMessage.NegotiateFlags |= NegotiateFlags.Use56BitEncryption; + } + if ((negotiateMessage.NegotiateFlags & NegotiateFlags.Use128BitEncryption) > 0) + { + challengeMessage.NegotiateFlags |= NegotiateFlags.Use128BitEncryption; + } + challengeMessage.TargetName = Environment.MachineName; + challengeMessage.ServerChallenge = serverChallenge; + challengeMessage.TargetInfo = AVPairUtils.GetAVPairSequence(Environment.MachineName, Environment.MachineName); + challengeMessage.Version = NTLMVersion.Server2003; + return Win32Error.ERROR_SUCCESS; + } + + public override Win32Error Authenticate(object context, AuthenticateMessage message) + { + AuthContext authContext = context as AuthContext; + if (authContext == null) + { + return Win32Error.ERROR_NO_TOKEN; + } + + authContext.UserName = message.UserName; + authContext.SessionKey = message.EncryptedRandomSessionKey; + if ((message.NegotiateFlags & NegotiateFlags.Anonymous) > 0) + { + if (this.EnableGuestLogin) + { + authContext.IsGuest = true; + return Win32Error.ERROR_SUCCESS; + } + else + { + return Win32Error.ERROR_LOGON_FAILURE; + } + } + + string password = m_GetUserPassword(message.UserName); + if (password == null) + { + if (this.EnableGuestLogin) + { + authContext.IsGuest = true; + return Win32Error.ERROR_SUCCESS; + } + else + { + return Win32Error.ERROR_LOGON_FAILURE; + } + } + + bool success; + byte[] serverChallenge = authContext.ServerChallenge; + if ((message.NegotiateFlags & NegotiateFlags.ExtendedSecurity) > 0) + { + if (AuthenticationMessageUtils.IsNTLMv1ExtendedSecurity(message.LmChallengeResponse)) + { + // NTLM v1 Extended Security: + success = AuthenticateV1Extended(password, serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse); + } + else + { + // NTLM v2: + success = AuthenticateV2(message.DomainName, message.UserName, password, serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse); + } + } + else + { + success = AuthenticateV1(password, serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse); + } + + if (success) + { + return Win32Error.ERROR_SUCCESS; + } + else + { + return Win32Error.ERROR_LOGON_FAILURE; + } + } + + public override void DeleteSecurityContext(ref object context) + { + } + + public override object GetContextAttribute(object context, GSSAttributeName attributeName) + { + AuthContext authContext = context as AuthContext; + if (authContext != null) + { + switch (attributeName) + { + case GSSAttributeName.IsGuest: + return authContext.IsGuest; + case GSSAttributeName.MachineName: + return authContext.WorkStation; + case GSSAttributeName.SessionKey: + return authContext.SessionKey; + case GSSAttributeName.UserName: + return authContext.UserName; + } + } + + return null; + } + + private bool EnableGuestLogin + { + get + { + return (m_GetUserPassword("Guest") == String.Empty); + } + } + + /// + /// LM v1 / NTLM v1 + /// + private static bool AuthenticateV1(string password, byte[] serverChallenge, byte[] lmResponse, byte[] ntResponse) + { + byte[] expectedLMResponse = NTLMCryptography.ComputeLMv1Response(serverChallenge, password); + if (ByteUtils.AreByteArraysEqual(expectedLMResponse, lmResponse)) + { + return true; + } + + byte[] expectedNTResponse = NTLMCryptography.ComputeNTLMv1Response(serverChallenge, password); + return ByteUtils.AreByteArraysEqual(expectedNTResponse, ntResponse); + } + + /// + /// LM v1 / NTLM v1 Extended Security + /// + private static bool AuthenticateV1Extended(string password, byte[] serverChallenge, byte[] lmResponse, byte[] ntResponse) + { + byte[] clientChallenge = ByteReader.ReadBytes(lmResponse, 0, 8); + byte[] expectedNTLMv1Response = NTLMCryptography.ComputeNTLMv1ExtendedSecurityResponse(serverChallenge, clientChallenge, password); + + return ByteUtils.AreByteArraysEqual(expectedNTLMv1Response, ntResponse); + } + + /// + /// LM v2 / NTLM v2 + /// + private bool AuthenticateV2(string domainName, string accountName, string password, byte[] serverChallenge, byte[] lmResponse, byte[] ntResponse) + { + byte[] _LMv2ClientChallenge = ByteReader.ReadBytes(lmResponse, 16, 8); + byte[] expectedLMv2Response = NTLMCryptography.ComputeLMv2Response(serverChallenge, _LMv2ClientChallenge, password, accountName, domainName); + if (ByteUtils.AreByteArraysEqual(expectedLMv2Response, lmResponse)) + { + return true; + } + + if (AuthenticationMessageUtils.IsNTLMv2NTResponse(ntResponse)) + { + byte[] clientNTProof = ByteReader.ReadBytes(ntResponse, 0, 16); + byte[] clientChallengeStructurePadded = ByteReader.ReadBytes(ntResponse, 16, ntResponse.Length - 16); + byte[] expectedNTProof = NTLMCryptography.ComputeNTLMv2Proof(serverChallenge, clientChallengeStructurePadded, password, accountName, domainName); + + return ByteUtils.AreByteArraysEqual(clientNTProof, expectedNTProof); + } + return false; + } + + /// + /// Generate 8-byte server challenge + /// + private static byte[] GenerateServerChallenge() + { + byte[] serverChallenge = new byte[8]; + new Random().NextBytes(serverChallenge); + return serverChallenge; + } + } +} diff --git a/SMBLibrary/Authentication/NTLM/NTLMAuthenticationProviderBase.cs b/SMBLibrary/Authentication/NTLM/NTLMAuthenticationProviderBase.cs new file mode 100644 index 0000000..90a92bb --- /dev/null +++ b/SMBLibrary/Authentication/NTLM/NTLMAuthenticationProviderBase.cs @@ -0,0 +1,23 @@ +/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. + * + * You can redistribute this program and/or modify it under the terms of + * the GNU Lesser Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + */ +using System; +using System.Collections.Generic; +using SMBLibrary.Authentication.GSSAPI; + +namespace SMBLibrary.Authentication.NTLM +{ + public abstract class NTLMAuthenticationProviderBase + { + public abstract Win32Error GetChallengeMessage(out object context, NegotiateMessage negotiateMessage, out ChallengeMessage challengeMessage); + + public abstract Win32Error Authenticate(object context, AuthenticateMessage authenticateMessage); + + public abstract void DeleteSecurityContext(ref object context); + + public abstract object GetContextAttribute(object context, GSSAttributeName attributeName); + } +} diff --git a/SMBLibrary/Enums/NTStatus.cs b/SMBLibrary/Enums/NTStatus.cs index d1cfaa2..d7c9322 100644 --- a/SMBLibrary/Enums/NTStatus.cs +++ b/SMBLibrary/Enums/NTStatus.cs @@ -25,6 +25,10 @@ namespace SMBLibrary STATUS_FILE_LOCK_CONFLICT = 0xC0000054, STATUS_LOGON_FAILURE = 0xC000006D, // Authentication failure. STATUS_ACCOUNT_RESTRICTION = 0xC000006E, // The user has an empty password, which is not allowed + STATUS_INVALID_LOGON_HOURS = 0xC000006F, + STATUS_INVALID_WORKSTATION = 0xC0000070, + STATUS_PASSWORD_EXPIRED = 0xC0000071, + STATUS_ACCOUNT_DISABLED = 0xC0000072, STATUS_DISK_FULL = 0xC000007F, STATUS_INSUFFICIENT_RESOURCES = 0xC000009A, STATUS_MEDIA_WRITE_PROTECTED = 0xC00000A2, @@ -36,9 +40,13 @@ namespace SMBLibrary STATUS_TOO_MANY_OPENED_FILES = 0xC000011F, STATUS_CANNOT_DELETE = 0xC0000121, STATUS_FILE_CLOSED = 0xC0000128, + STATUS_LOGON_TYPE_NOT_GRANTED = 0xC000015B, + STATUS_ACCOUNT_EXPIRED = 0xC0000193, STATUS_FS_DRIVER_REQUIRED = 0xC000019C, STATUS_USER_SESSION_DELETED = 0xC0000203, STATUS_INSUFF_SERVER_RESOURCES = 0xC0000205, + STATUS_ACCOUNT_LOCKED_OUT = 0xC0000234, + STATUS_PASSWORD_MUST_CHANGE = 0xC0000244, STATUS_INVALID_SMB = 0x00010002, // SMB1/CIFS: A corrupt or invalid SMB request was received STATUS_SMB_BAD_COMMAND = 0x00160002, // SMB1/CIFS: An unknown SMB command code was received by the server diff --git a/SMBLibrary/Enums/Win32Error.cs b/SMBLibrary/Enums/Win32Error.cs index bd44d67..5740af9 100644 --- a/SMBLibrary/Enums/Win32Error.cs +++ b/SMBLibrary/Enums/Win32Error.cs @@ -10,10 +10,17 @@ namespace SMBLibrary ERROR_DISK_FULL = 0x0070, ERROR_DIR_NOT_EMPTY = 0x0091, ERROR_ALREADY_EXISTS = 0x00B7, + ERROR_NO_TOKEN = 0x03F0, ERROR_LOGON_FAILURE = 0x052E, ERROR_ACCOUNT_RESTRICTION = 0x052F, + ERROR_INVALID_LOGON_HOURS = 0x0530, + ERROR_INVALID_WORKSTATION = 0x0531, + ERROR_PASSWORD_EXPIRED = 0x0532, ERROR_ACCOUNT_DISABLED = 0x0533, ERROR_LOGON_TYPE_NOT_GRANTED = 0x0569, + ERROR_ACCOUNT_EXPIRED = 0x0701, + ERROR_PASSWORD_MUST_CHANGE = 0x0773, + ERROR_ACCOUNT_LOCKED_OUT = 0x0775, NERR_NetNameNotFound = 0x0906, } } diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index cba26b5..22fdc64 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -31,6 +31,7 @@ + @@ -40,6 +41,8 @@ + + @@ -181,12 +184,10 @@ - + - - @@ -222,8 +223,6 @@ - - @@ -532,12 +531,12 @@ + - diff --git a/SMBLibrary/Server/ConnectionState/ConnectionState.cs b/SMBLibrary/Server/ConnectionState/ConnectionState.cs index c4b2068..9f6ab8e 100644 --- a/SMBLibrary/Server/ConnectionState/ConnectionState.cs +++ b/SMBLibrary/Server/ConnectionState/ConnectionState.cs @@ -30,6 +30,7 @@ namespace SMBLibrary.Server public NBTConnectionReceiveBuffer ReceiveBuffer; protected LogDelegate LogToServerHandler; public SMBDialect ServerDialect; + public object AuthenticationContext; public ConnectionState(LogDelegate logToServerHandler) { diff --git a/SMBLibrary/Server/Exceptions/EmptyPasswordNotAllowedException.cs b/SMBLibrary/Server/Exceptions/EmptyPasswordNotAllowedException.cs deleted file mode 100644 index 72214d3..0000000 --- a/SMBLibrary/Server/Exceptions/EmptyPasswordNotAllowedException.cs +++ /dev/null @@ -1,23 +0,0 @@ -/* Copyright (C) 2014 Tal Aloni . All rights reserved. - * - * You can redistribute this program and/or modify it under the terms of - * the GNU Lesser Public License as published by the Free Software Foundation, - * either version 3 of the License, or (at your option) any later version. - */ -using System; -using System.Collections.Generic; -using System.Text; - -namespace SMBLibrary.Server -{ - public class EmptyPasswordNotAllowedException : Exception - { - public EmptyPasswordNotAllowedException() : base() - { - } - - public EmptyPasswordNotAllowedException(string message) : base(message) - { - } - } -} diff --git a/SMBLibrary/Server/Helpers/LogonHelper.cs b/SMBLibrary/Server/Helpers/LogonHelper.cs new file mode 100644 index 0000000..74fb7dd --- /dev/null +++ b/SMBLibrary/Server/Helpers/LogonHelper.cs @@ -0,0 +1,42 @@ +/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. + * + * You can redistribute this program and/or modify it under the terms of + * the GNU Lesser Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + */ +using System; +using System.Collections.Generic; +using Utilities; + +namespace SMBLibrary.Server +{ + public class LogonHelper + { + public static NTStatus ToNTStatus(Win32Error errorCode) + { + switch (errorCode) + { + case Win32Error.ERROR_ACCOUNT_RESTRICTION: + return NTStatus.STATUS_ACCOUNT_RESTRICTION; + case Win32Error.ERROR_INVALID_LOGON_HOURS: + return NTStatus.STATUS_INVALID_LOGON_HOURS; + case Win32Error.ERROR_INVALID_WORKSTATION: + return NTStatus.STATUS_INVALID_WORKSTATION; + case Win32Error.ERROR_PASSWORD_EXPIRED: + return NTStatus.STATUS_PASSWORD_EXPIRED; + case Win32Error.ERROR_ACCOUNT_DISABLED: + return NTStatus.STATUS_ACCOUNT_DISABLED; + case Win32Error.ERROR_LOGON_TYPE_NOT_GRANTED: + return NTStatus.STATUS_LOGON_TYPE_NOT_GRANTED; + case Win32Error.ERROR_ACCOUNT_EXPIRED: + return NTStatus.STATUS_ACCOUNT_EXPIRED; + case Win32Error.ERROR_PASSWORD_MUST_CHANGE: + return NTStatus.STATUS_PASSWORD_MUST_CHANGE; + case Win32Error.ERROR_ACCOUNT_LOCKED_OUT: + return NTStatus.STATUS_ACCOUNT_LOCKED_OUT; + default: + return NTStatus.STATUS_LOGON_FAILURE; + } + } + } +} diff --git a/SMBLibrary/Server/INTLMAuthenticationProvider.cs b/SMBLibrary/Server/INTLMAuthenticationProvider.cs deleted file mode 100644 index 9320345..0000000 --- a/SMBLibrary/Server/INTLMAuthenticationProvider.cs +++ /dev/null @@ -1,33 +0,0 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. - * - * You can redistribute this program and/or modify it under the terms of - * the GNU Lesser Public License as published by the Free Software Foundation, - * either version 3 of the License, or (at your option) any later version. - */ -using System; -using System.Collections.Generic; -using System.Text; -using SMBLibrary.Authentication.NTLM; - -namespace SMBLibrary.Server -{ - public interface INTLMAuthenticationProvider - { - ChallengeMessage GetChallengeMessage(NegotiateMessage negotiateMessage); - - bool Authenticate(AuthenticateMessage authenticateMessage); - - /// - /// Permit access to this user via the guest user account if the normal authentication process fails. - /// - /// - /// Windows will permit fallback when these conditions are met: - /// 1. The guest user account is enabled. - /// 2. The guest user account does not have a password set. - /// 3. The specified account does not exist. - /// OR: - /// The password is correct but 'limitblankpassworduse' is set to 1 (logon over a network is disabled for accounts without a password). - /// - bool FallbackToGuest(string userName); - } -} diff --git a/SMBLibrary/Server/IndependentUserCollection.cs b/SMBLibrary/Server/IndependentUserCollection.cs deleted file mode 100644 index e06a655..0000000 --- a/SMBLibrary/Server/IndependentUserCollection.cs +++ /dev/null @@ -1,206 +0,0 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. - * - * You can redistribute this program and/or modify it under the terms of - * the GNU Lesser Public License as published by the Free Software Foundation, - * either version 3 of the License, or (at your option) any later version. - */ -using System; -using System.Collections.Generic; -using System.Text; -using Utilities; -using SMBLibrary.Authentication; -using SMBLibrary.Authentication.NTLM; - -namespace SMBLibrary.Server -{ - public class IndependentUserCollection : UserCollection, INTLMAuthenticationProvider - { - private byte[] m_serverChallenge = new byte[8]; - - public IndependentUserCollection() - { - } - - public IndependentUserCollection(UserCollection users) - { - this.AddRange(users); - } - - /// - /// LM v1 / NTLM v1 - /// - private User AuthenticateV1(string accountNameToAuth, byte[] serverChallenge, byte[] lmResponse, byte[] ntResponse) - { - for (int index = 0; index < this.Count; index++) - { - string accountName = this[index].AccountName; - string password = this[index].Password; - - if (String.Equals(accountName, accountNameToAuth, StringComparison.InvariantCultureIgnoreCase)) - { - byte[] expectedLMResponse = NTLMCryptography.ComputeLMv1Response(serverChallenge, password); - if (ByteUtils.AreByteArraysEqual(expectedLMResponse, lmResponse)) - { - return this[index]; - } - - byte[] expectedNTResponse = NTLMCryptography.ComputeNTLMv1Response(serverChallenge, password); - if (ByteUtils.AreByteArraysEqual(expectedNTResponse, ntResponse)) - { - return this[index]; - } - } - } - return null; - } - - /// - /// LM v1 / NTLM v1 Extended Security - /// - private User AuthenticateV1Extended(string accountNameToAuth, byte[] serverChallenge, byte[] lmResponse, byte[] ntResponse) - { - for (int index = 0; index < this.Count; index++) - { - string accountName = this[index].AccountName; - string password = this[index].Password; - - if (String.Equals(accountName, accountNameToAuth, StringComparison.InvariantCultureIgnoreCase)) - { - byte[] clientChallenge = ByteReader.ReadBytes(lmResponse, 0, 8); - byte[] expectedNTLMv1Response = NTLMCryptography.ComputeNTLMv1ExtendedSecurityResponse(serverChallenge, clientChallenge, password); - - if (ByteUtils.AreByteArraysEqual(expectedNTLMv1Response, ntResponse)) - { - return this[index]; - } - } - } - return null; - } - - /// - /// LM v2 / NTLM v2 - /// - private User AuthenticateV2(string domainNameToAuth, string accountNameToAuth, byte[] serverChallenge, byte[] lmResponse, byte[] ntResponse) - { - for (int index = 0; index < this.Count; index++) - { - string accountName = this[index].AccountName; - string password = this[index].Password; - - if (String.Equals(accountName, accountNameToAuth, StringComparison.InvariantCultureIgnoreCase)) - { - byte[] _LMv2ClientChallenge = ByteReader.ReadBytes(lmResponse, 16, 8); - byte[] expectedLMv2Response = NTLMCryptography.ComputeLMv2Response(serverChallenge, _LMv2ClientChallenge, password, accountName, domainNameToAuth); - if (ByteUtils.AreByteArraysEqual(expectedLMv2Response, lmResponse)) - { - return this[index]; - } - - if (AuthenticationMessageUtils.IsNTLMv2NTResponse(ntResponse)) - { - byte[] clientNTProof = ByteReader.ReadBytes(ntResponse, 0, 16); - byte[] clientChallengeStructurePadded = ByteReader.ReadBytes(ntResponse, 16, ntResponse.Length - 16); - byte[] expectedNTProof = NTLMCryptography.ComputeNTLMv2Proof(serverChallenge, clientChallengeStructurePadded, password, accountName, domainNameToAuth); - - if (ByteUtils.AreByteArraysEqual(clientNTProof, expectedNTProof)) - { - return this[index]; - } - } - } - } - return null; - } - - private byte[] GenerateServerChallenge() - { - new Random().NextBytes(m_serverChallenge); - return m_serverChallenge; - } - - public ChallengeMessage GetChallengeMessage(NegotiateMessage negotiateMessage) - { - byte[] serverChallenge = GenerateServerChallenge(); - - ChallengeMessage message = new ChallengeMessage(); - message.NegotiateFlags = NegotiateFlags.UnicodeEncoding | - NegotiateFlags.TargetNameSupplied | - NegotiateFlags.NTLMKey | - NegotiateFlags.TargetTypeServer | - NegotiateFlags.ExtendedSecurity | - NegotiateFlags.TargetInfo | - NegotiateFlags.Version; - if ((negotiateMessage.NegotiateFlags & NegotiateFlags.Sign) > 0) - { - // [MS-NLMP] If the client sends NTLMSSP_NEGOTIATE_SIGN to the server in the NEGOTIATE_MESSAGE, - // the server MUST return NTLMSSP_NEGOTIATE_SIGN to the client in the CHALLENGE_MESSAGE. - message.NegotiateFlags |= NegotiateFlags.Sign; - } - if ((negotiateMessage.NegotiateFlags & NegotiateFlags.Use56BitEncryption) > 0) - { - message.NegotiateFlags |= NegotiateFlags.Use56BitEncryption; - } - if ((negotiateMessage.NegotiateFlags & NegotiateFlags.Use128BitEncryption) > 0) - { - message.NegotiateFlags |= NegotiateFlags.Use128BitEncryption; - } - message.TargetName = Environment.MachineName; - message.ServerChallenge = serverChallenge; - message.TargetInfo = AVPairUtils.GetAVPairSequence(Environment.MachineName, Environment.MachineName); - message.Version = NTLMVersion.Server2003; - return message; - } - - public bool Authenticate(AuthenticateMessage message) - { - if ((message.NegotiateFlags & NegotiateFlags.Anonymous) > 0) - { - return this.EnableGuestLogin; - } - - User user; - if ((message.NegotiateFlags & NegotiateFlags.ExtendedSecurity) > 0) - { - if (AuthenticationMessageUtils.IsNTLMv1ExtendedSecurity(message.LmChallengeResponse)) - { - // NTLM v1 Extended Security: - user = AuthenticateV1Extended(message.UserName, m_serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse); - } - else - { - // NTLM v2: - user = AuthenticateV2(message.DomainName, message.UserName, m_serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse); - } - } - else - { - user = AuthenticateV1(message.UserName, m_serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse); - } - - return (user != null); - } - - public bool FallbackToGuest(string userName) - { - return (EnableGuestLogin && (IndexOf(userName) == -1)); - } - - private bool EnableGuestLogin - { - get - { - int index = IndexOf("Guest"); - return (index >= 0 && this[index].Password == String.Empty); - } - } - - public byte[] ServerChallenge - { - get - { - return m_serverChallenge; - } - } - } -} diff --git a/SMBLibrary/Server/SMB1/NegotiateHelper.cs b/SMBLibrary/Server/SMB1/NegotiateHelper.cs index 9da05e7..69211b3 100644 --- a/SMBLibrary/Server/SMB1/NegotiateHelper.cs +++ b/SMBLibrary/Server/SMB1/NegotiateHelper.cs @@ -19,7 +19,7 @@ namespace SMBLibrary.Server.SMB1 /// public class NegotiateHelper { - internal static NegotiateResponseNTLM GetNegotiateResponse(SMB1Header header, NegotiateRequest request, INTLMAuthenticationProvider users) + internal static NegotiateResponseNTLM GetNegotiateResponse(SMB1Header header, NegotiateRequest request, NTLMAuthenticationProviderBase securityProvider, ConnectionState state) { NegotiateResponseNTLM response = new NegotiateResponseNTLM(); @@ -38,8 +38,13 @@ namespace SMBLibrary.Server.SMB1 ServerCapabilities.LargeWrite; response.SystemTime = DateTime.UtcNow; response.ServerTimeZone = (short)-TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).TotalMinutes; - ChallengeMessage challengeMessage = users.GetChallengeMessage(CreateNegotiateMessage()); - response.Challenge = challengeMessage.ServerChallenge; + NegotiateMessage negotiateMessage = CreateNegotiateMessage(); + ChallengeMessage challengeMessage; + Win32Error status = securityProvider.GetChallengeMessage(out state.AuthenticationContext, negotiateMessage, out challengeMessage); + if (status == Win32Error.ERROR_SUCCESS) + { + response.Challenge = challengeMessage.ServerChallenge; + } response.DomainName = String.Empty; response.ServerName = String.Empty; diff --git a/SMBLibrary/Server/SMB1/SessionSetupHelper.cs b/SMBLibrary/Server/SMB1/SessionSetupHelper.cs index f0bd480..5d7940e 100644 --- a/SMBLibrary/Server/SMB1/SessionSetupHelper.cs +++ b/SMBLibrary/Server/SMB1/SessionSetupHelper.cs @@ -19,56 +19,43 @@ namespace SMBLibrary.Server.SMB1 /// public class SessionSetupHelper { - internal static SMB1Command GetSessionSetupResponse(SMB1Header header, SessionSetupAndXRequest request, INTLMAuthenticationProvider users, SMB1ConnectionState state) + internal static SMB1Command GetSessionSetupResponse(SMB1Header header, SessionSetupAndXRequest request, NTLMAuthenticationProviderBase securityProvider, SMB1ConnectionState state) { SessionSetupAndXResponse response = new SessionSetupAndXResponse(); // The PrimaryDomain field in the request is used to determine with domain controller should authenticate the user credentials, // However, the domain controller itself does not use this field. // See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa378749%28v=vs.85%29.aspx AuthenticateMessage message = CreateAuthenticateMessage(request.AccountName, request.OEMPassword, request.UnicodePassword); - bool loginSuccess; - try + Win32Error loginStatus = securityProvider.Authenticate(state.AuthenticationContext, message); + if (loginStatus != Win32Error.ERROR_SUCCESS) { - loginSuccess = users.Authenticate(message); - } - catch (EmptyPasswordNotAllowedException) - { - state.LogToServer(Severity.Information, "User '{0}' authentication using an empty password was rejected", message.UserName); - header.Status = NTStatus.STATUS_ACCOUNT_RESTRICTION; + state.LogToServer(Severity.Information, "User '{0}' failed authentication. Win32 error: {1}", message.UserName, loginStatus); + header.Status = LogonHelper.ToNTStatus(loginStatus); return new ErrorResponse(request.CommandName); } - if (loginSuccess) + bool? isGuest = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.IsGuest) as bool?; + SMB1Session session; + if (!isGuest.HasValue || !isGuest.Value) { state.LogToServer(Severity.Information, "User '{0}' authenticated successfully", message.UserName); - SMB1Session session = state.CreateSession(message.UserName, message.WorkStation); - if (session == null) - { - header.Status = NTStatus.STATUS_TOO_MANY_SESSIONS; - return new ErrorResponse(request.CommandName); - } - header.UID = session.UserID; - response.PrimaryDomain = request.PrimaryDomain; - } - else if (users.FallbackToGuest(message.UserName)) - { - state.LogToServer(Severity.Information, "User '{0}' failed authentication. logged in as guest", message.UserName); - SMB1Session session = state.CreateSession("Guest", message.WorkStation); - if (session == null) - { - header.Status = NTStatus.STATUS_TOO_MANY_SESSIONS; - return new ErrorResponse(request.CommandName); - } - header.UID = session.UserID; - response.Action = SessionSetupAction.SetupGuest; - response.PrimaryDomain = request.PrimaryDomain; + session = state.CreateSession(message.UserName, message.WorkStation); } else { - state.LogToServer(Severity.Information, "User '{0}' failed authentication", message.UserName); - header.Status = NTStatus.STATUS_LOGON_FAILURE; + state.LogToServer(Severity.Information, "User '{0}' failed authentication. logged in as guest", message.UserName); + session = state.CreateSession("Guest", message.WorkStation); + response.Action = SessionSetupAction.SetupGuest; + } + + if (session == null) + { + header.Status = NTStatus.STATUS_TOO_MANY_SESSIONS; return new ErrorResponse(request.CommandName); } + + header.UID = session.UserID; + response.PrimaryDomain = request.PrimaryDomain; if ((request.Capabilities & ServerCapabilities.LargeRead) > 0) { state.LargeRead = true; @@ -83,7 +70,7 @@ namespace SMBLibrary.Server.SMB1 return response; } - internal static SMB1Command GetSessionSetupResponseExtended(SMB1Header header, SessionSetupAndXRequestExtended request, INTLMAuthenticationProvider users, SMB1ConnectionState state) + internal static SMB1Command GetSessionSetupResponseExtended(SMB1Header header, SessionSetupAndXRequestExtended request, NTLMAuthenticationProviderBase securityProvider, SMB1ConnectionState state) { SessionSetupAndXResponseExtended response = new SessionSetupAndXResponseExtended(); @@ -117,7 +104,14 @@ namespace SMBLibrary.Server.SMB1 if (messageType == MessageTypeName.Negotiate) { NegotiateMessage negotiateMessage = new NegotiateMessage(messageBytes); - ChallengeMessage challengeMessage = users.GetChallengeMessage(negotiateMessage); + ChallengeMessage challengeMessage; + Win32Error status = securityProvider.GetChallengeMessage(out state.AuthenticationContext, negotiateMessage, out challengeMessage); + if (status != Win32Error.ERROR_SUCCESS) + { + header.Status = NTStatus.STATUS_LOGON_FAILURE; + return new ErrorResponse(request.CommandName); + } + if (isRawMessage) { response.SecurityBlob = challengeMessage.GetBytes(); @@ -131,35 +125,26 @@ namespace SMBLibrary.Server.SMB1 else // MessageTypeName.Authenticate { AuthenticateMessage authenticateMessage = new AuthenticateMessage(messageBytes); - bool loginSuccess; - try + Win32Error loginStatus = securityProvider.Authenticate(state.AuthenticationContext, authenticateMessage); + if (loginStatus != Win32Error.ERROR_SUCCESS) { - loginSuccess = users.Authenticate(authenticateMessage); - } - catch (EmptyPasswordNotAllowedException) - { - state.LogToServer(Severity.Information, "User '{0}' authentication using an empty password was rejected", authenticateMessage.UserName); - header.Status = NTStatus.STATUS_ACCOUNT_RESTRICTION; + state.LogToServer(Severity.Information, "User '{0}' failed authentication. Win32 error: {0}", authenticateMessage.UserName, loginStatus); + header.Status = LogonHelper.ToNTStatus(loginStatus); return new ErrorResponse(request.CommandName); } - if (loginSuccess) + bool? isGuest = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.IsGuest) as bool?; + if (!isGuest.HasValue || !isGuest.Value) { state.LogToServer(Severity.Information, "User '{0}' authenticated successfully", authenticateMessage.UserName); state.CreateSession(header.UID, authenticateMessage.UserName, authenticateMessage.WorkStation); } - else if (users.FallbackToGuest(authenticateMessage.UserName)) + else { state.LogToServer(Severity.Information, "User '{0}' failed authentication. logged in as guest", authenticateMessage.UserName); state.CreateSession(header.UID, "Guest", authenticateMessage.WorkStation); response.Action = SessionSetupAction.SetupGuest; } - else - { - state.LogToServer(Severity.Information, "User '{0}' failed authentication", authenticateMessage.UserName); - header.Status = NTStatus.STATUS_LOGON_FAILURE; - return new ErrorResponse(request.CommandName); - } if (!isRawMessage) { diff --git a/SMBLibrary/Server/SMB2/SessionSetupHelper.cs b/SMBLibrary/Server/SMB2/SessionSetupHelper.cs index 0140f1c..2e67302 100644 --- a/SMBLibrary/Server/SMB2/SessionSetupHelper.cs +++ b/SMBLibrary/Server/SMB2/SessionSetupHelper.cs @@ -18,7 +18,7 @@ namespace SMBLibrary.Server.SMB2 /// public class SessionSetupHelper { - internal static SMB2Command GetSessionSetupResponse(SessionSetupRequest request, INTLMAuthenticationProvider users, SMB2ConnectionState state) + internal static SMB2Command GetSessionSetupResponse(SessionSetupRequest request, NTLMAuthenticationProviderBase securityProvider, SMB2ConnectionState state) { // [MS-SMB2] Windows [..] will also accept raw Kerberos messages and implicit NTLM messages as part of GSS authentication. SessionSetupResponse response = new SessionSetupResponse(); @@ -49,7 +49,13 @@ namespace SMBLibrary.Server.SMB2 if (messageType == MessageTypeName.Negotiate) { NegotiateMessage negotiateMessage = new NegotiateMessage(messageBytes); - ChallengeMessage challengeMessage = users.GetChallengeMessage(negotiateMessage); + ChallengeMessage challengeMessage; + Win32Error status = securityProvider.GetChallengeMessage(out state.AuthenticationContext, negotiateMessage, out challengeMessage); + if (status != Win32Error.ERROR_SUCCESS) + { + return new ErrorResponse(request.CommandName, NTStatus.STATUS_LOGON_FAILURE); + } + if (isRawMessage) { response.SecurityBuffer = challengeMessage.GetBytes(); @@ -63,33 +69,26 @@ namespace SMBLibrary.Server.SMB2 else // MessageTypeName.Authenticate { AuthenticateMessage authenticateMessage = new AuthenticateMessage(messageBytes); - bool loginSuccess; - try + Win32Error loginStatus = securityProvider.Authenticate(state.AuthenticationContext, authenticateMessage); + if (loginStatus != Win32Error.ERROR_SUCCESS) { - loginSuccess = users.Authenticate(authenticateMessage); - } - catch (EmptyPasswordNotAllowedException) - { - state.LogToServer(Severity.Information, "User '{0}' authentication using an empty password was rejected", authenticateMessage.UserName); - return new ErrorResponse(request.CommandName, NTStatus.STATUS_ACCOUNT_RESTRICTION); + state.LogToServer(Severity.Information, "User '{0}' failed authentication. Win32 error: {1}", authenticateMessage.UserName, loginStatus); + NTStatus status = LogonHelper.ToNTStatus(loginStatus); + return new ErrorResponse(request.CommandName, status); } - if (loginSuccess) + bool? isGuest = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.IsGuest) as bool?; + if (!isGuest.HasValue || !isGuest.Value) { state.LogToServer(Severity.Information, "User '{0}' authenticated successfully", authenticateMessage.UserName); state.CreateSession(request.Header.SessionID, authenticateMessage.UserName, authenticateMessage.WorkStation); } - else if (users.FallbackToGuest(authenticateMessage.UserName)) + else { state.LogToServer(Severity.Information, "User '{0}' failed authentication. logged in as guest", authenticateMessage.UserName); state.CreateSession(request.Header.SessionID, "Guest", authenticateMessage.WorkStation); response.SessionFlags = SessionFlags.IsGuest; } - else - { - state.LogToServer(Severity.Information, "User '{0}' failed authentication", authenticateMessage.UserName); - return new ErrorResponse(request.CommandName, NTStatus.STATUS_LOGON_FAILURE); - } if (!isRawMessage) { diff --git a/SMBLibrary/Server/SMBServer.SMB1.cs b/SMBLibrary/Server/SMBServer.SMB1.cs index 54b76f8..19ad77f 100644 --- a/SMBLibrary/Server/SMBServer.SMB1.cs +++ b/SMBLibrary/Server/SMBServer.SMB1.cs @@ -68,7 +68,7 @@ namespace SMBLibrary.Server } else { - return NegotiateHelper.GetNegotiateResponse(header, request, m_users); + return NegotiateHelper.GetNegotiateResponse(header, request, m_securityProvider, state); } } else @@ -102,13 +102,13 @@ namespace SMBLibrary.Server { SessionSetupAndXRequest request = (SessionSetupAndXRequest)command; state.MaxBufferSize = request.MaxBufferSize; - return SessionSetupHelper.GetSessionSetupResponse(header, request, m_users, state); + return SessionSetupHelper.GetSessionSetupResponse(header, request, m_securityProvider, state); } else if (command is SessionSetupAndXRequestExtended) { SessionSetupAndXRequestExtended request = (SessionSetupAndXRequestExtended)command; state.MaxBufferSize = request.MaxBufferSize; - return SessionSetupHelper.GetSessionSetupResponseExtended(header, request, m_users, state); + return SessionSetupHelper.GetSessionSetupResponseExtended(header, request, m_securityProvider, state); } else if (command is EchoRequest) { diff --git a/SMBLibrary/Server/SMBServer.SMB2.cs b/SMBLibrary/Server/SMBServer.SMB2.cs index 2cc76eb..5eaa6fa 100644 --- a/SMBLibrary/Server/SMBServer.SMB2.cs +++ b/SMBLibrary/Server/SMBServer.SMB2.cs @@ -109,7 +109,7 @@ namespace SMBLibrary.Server { if (command is SessionSetupRequest) { - return SessionSetupHelper.GetSessionSetupResponse((SessionSetupRequest)command, m_users, state); + return SessionSetupHelper.GetSessionSetupResponse((SessionSetupRequest)command, m_securityProvider, state); } else if (command is EchoRequest) { diff --git a/SMBLibrary/Server/SMBServer.cs b/SMBLibrary/Server/SMBServer.cs index 4d008d4..ebe03d1 100644 --- a/SMBLibrary/Server/SMBServer.cs +++ b/SMBLibrary/Server/SMBServer.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; +using SMBLibrary.Authentication.NTLM; using SMBLibrary.NetBios; using SMBLibrary.Services; using SMBLibrary.SMB1; @@ -24,7 +25,7 @@ namespace SMBLibrary.Server public const bool EnableExtendedSecurity = true; private ShareCollection m_shares; // e.g. Shared folders - private INTLMAuthenticationProvider m_users; + private NTLMAuthenticationProviderBase m_securityProvider; private NamedPipeShare m_services; // Named pipes private IPAddress m_serverAddress; private SMBTransportType m_transport; @@ -37,14 +38,14 @@ namespace SMBLibrary.Server public event EventHandler OnLogEntry; - public SMBServer(ShareCollection shares, INTLMAuthenticationProvider users, IPAddress serverAddress, SMBTransportType transport) : this(shares, users, serverAddress, transport, true, true) + public SMBServer(ShareCollection shares, NTLMAuthenticationProviderBase securityProvider, IPAddress serverAddress, SMBTransportType transport) : this(shares, securityProvider, serverAddress, transport, true, true) { } - public SMBServer(ShareCollection shares, INTLMAuthenticationProvider users, IPAddress serverAddress, SMBTransportType transport, bool enableSMB1, bool enableSMB2) + public SMBServer(ShareCollection shares, NTLMAuthenticationProviderBase securityProvider, IPAddress serverAddress, SMBTransportType transport, bool enableSMB1, bool enableSMB2) { m_shares = shares; - m_users = users; + m_securityProvider = securityProvider; m_serverAddress = serverAddress; m_serverGuid = Guid.NewGuid(); m_transport = transport; diff --git a/SMBLibrary/Win32/IntegratedNTLMAuthenticationProvider.cs b/SMBLibrary/Win32/IntegratedNTLMAuthenticationProvider.cs new file mode 100644 index 0000000..b734a05 --- /dev/null +++ b/SMBLibrary/Win32/IntegratedNTLMAuthenticationProvider.cs @@ -0,0 +1,176 @@ +/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. + * + * You can redistribute this program and/or modify it under the terms of + * the GNU Lesser Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + */ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; +using System.Runtime.InteropServices; +using Utilities; +using SMBLibrary.Authentication.GSSAPI; +using SMBLibrary.Authentication.NTLM; +using Microsoft.Win32; + +namespace SMBLibrary.Win32.Security +{ + public class IntegratedNTLMAuthenticationProvider : NTLMAuthenticationProviderBase + { + public class AuthContext + { + public SecHandle ServerContext; + public string WorkStation; + public string UserName; + public byte[] SessionKey; + public bool IsGuest; + + public AuthContext(SecHandle serverContext, string workStation) + { + ServerContext = serverContext; + WorkStation = workStation; + } + } + + public override Win32Error GetChallengeMessage(out object context, NegotiateMessage negotiateMessage, out ChallengeMessage challengeMessage) + { + byte[] negotiateMessageBytes = negotiateMessage.GetBytes(); + SecHandle serverContext; + byte[] challengeMessageBytes; + try + { + challengeMessageBytes = SSPIHelper.GetType2Message(negotiateMessageBytes, out serverContext); + } + catch (Exception) + { + context = null; + challengeMessage = null; + return (Win32Error)Marshal.GetLastWin32Error(); + } + + context = new AuthContext(serverContext, negotiateMessage.Workstation); + challengeMessage = new ChallengeMessage(challengeMessageBytes); + return Win32Error.ERROR_SUCCESS; + } + + /// + /// Authenticate will return false when the password is correct in these cases: + /// 1. The correct password is blank and 'limitblankpassworduse' is set to 1. + /// 2. The user is listed in the "Deny access to this computer from the network" list. + /// + public override Win32Error Authenticate(object context, AuthenticateMessage message) + { + AuthContext authContext = context as AuthContext; + if (authContext == null) + { + return Win32Error.ERROR_NO_TOKEN; + } + + authContext.UserName = message.UserName; + authContext.SessionKey = message.EncryptedRandomSessionKey; + if ((message.NegotiateFlags & NegotiateFlags.Anonymous) > 0) + { + if (this.EnableGuestLogin) + { + authContext.IsGuest = true; + return Win32Error.ERROR_SUCCESS; + } + else + { + return Win32Error.ERROR_LOGON_FAILURE; + } + } + + byte[] messageBytes = message.GetBytes(); + bool success; + try + { + success = SSPIHelper.AuthenticateType3Message(authContext.ServerContext, messageBytes); + } + catch (Exception) + { + return (Win32Error)Marshal.GetLastWin32Error(); + } + + if (success) + { + return Win32Error.ERROR_SUCCESS; + } + else + { + Win32Error result = (Win32Error)Marshal.GetLastWin32Error(); + // Windows will permit fallback when these conditions are met: + // 1. The guest user account is enabled. + // 2. The guest user account does not have a password set. + // 3. The specified account does not exist. + // OR: + // The password is correct but 'limitblankpassworduse' is set to 1 (logon over a network is disabled for accounts without a password). + bool allowFallback = (!IsUserExists(message.UserName) || result == Win32Error.ERROR_ACCOUNT_RESTRICTION); + if (allowFallback && this.EnableGuestLogin) + { + authContext.IsGuest = true; + return Win32Error.ERROR_SUCCESS; + } + else + { + return result; + } + } + } + + public override void DeleteSecurityContext(ref object context) + { + AuthContext authContext = context as AuthContext; + if (authContext == null) + { + return; + } + + SecHandle handle = ((AuthContext)context).ServerContext; + SSPIHelper.DeleteSecurityContext(ref handle); + } + + public override object GetContextAttribute(object context, GSSAttributeName attributeName) + { + AuthContext authContext = context as AuthContext; + if (authContext != null) + { + switch (attributeName) + { + case GSSAttributeName.AccessToken: + return authContext.ServerContext; + case GSSAttributeName.IsGuest: + return authContext.IsGuest; + case GSSAttributeName.MachineName: + return authContext.WorkStation; + case GSSAttributeName.SessionKey: + return authContext.SessionKey; + case GSSAttributeName.UserName: + return authContext.UserName; + } + } + + return null; + } + + /// + /// We immitate Windows, Guest logins are disabled in any of these cases: + /// 1. The Guest account is disabled. + /// 2. The Guest account has password set. + /// 3. The Guest account is listed in the "deny access to this computer from the network" list. + /// + private bool EnableGuestLogin + { + get + { + return LoginAPI.ValidateUserPassword("Guest", String.Empty, LogonType.Network); + } + } + + public static bool IsUserExists(string userName) + { + return NetworkAPI.IsUserExists(userName); + } + } +} diff --git a/SMBLibrary/Win32/Win32UserCollection.cs b/SMBLibrary/Win32/Win32UserCollection.cs deleted file mode 100644 index 1f6e11d..0000000 --- a/SMBLibrary/Win32/Win32UserCollection.cs +++ /dev/null @@ -1,175 +0,0 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. - * - * You can redistribute this program and/or modify it under the terms of - * the GNU Lesser Public License as published by the Free Software Foundation, - * either version 3 of the License, or (at your option) any later version. - */ -using System; -using System.Collections.Generic; -using System.Net; -using System.Text; -using Utilities; -using SMBLibrary.Authentication.NTLM; -using SMBLibrary.Win32.Security; -using Microsoft.Win32; - -namespace SMBLibrary.Server.Win32 -{ - public class Win32UserCollection : UserCollection, INTLMAuthenticationProvider - { - private SecHandle m_serverContext; - private byte[] m_serverChallenge = new byte[8]; - - public Win32UserCollection() - { - List users = NetworkAPI.EnumerateNetworkUsers(); - foreach (string user in users) - { - this.Add(new User(user, String.Empty)); - } - } - - public ChallengeMessage GetChallengeMessage(NegotiateMessage negotiateMessage) - { - byte[] negotiateMessageBytes = negotiateMessage.GetBytes(); - byte[] challengeMessageBytes = SSPIHelper.GetType2Message(negotiateMessageBytes, out m_serverContext); - ChallengeMessage challengeMessage = new ChallengeMessage(challengeMessageBytes); - m_serverChallenge = challengeMessage.ServerChallenge; - return challengeMessage; - } - - /// - /// Authenticate will return false when the password is correct in these cases: - /// 1. The correct password is blank and 'limitblankpassworduse' is set to 1. - /// 2. The user is listed in the "Deny access to this computer from the network" list. - /// - public bool Authenticate(AuthenticateMessage message) - { - if ((message.NegotiateFlags & NegotiateFlags.Anonymous) > 0) - { - return this.EnableGuestLogin; - } - - // AuthenticateType3Message is not reliable when 'limitblankpassworduse' is set to 1 and the user has an empty password set. - // Note: Windows LogonUser API calls will be listed in the security event log. - if (!AreEmptyPasswordsAllowed() && - IsPasswordEmpty(message) && - LoginAPI.HasEmptyPassword(message.UserName)) - { - if (FallbackToGuest(message.UserName)) - { - return false; - } - else - { - throw new EmptyPasswordNotAllowedException(); - } - } - - byte[] messageBytes = message.GetBytes(); - try - { - return SSPIHelper.AuthenticateType3Message(m_serverContext, messageBytes); - } - catch (Exception) - { - return false; - } - } - - public bool IsPasswordEmpty(AuthenticateMessage message) - { - // See [MS-NLMP] 3.3.1 - NTLM v1 Authentication - // Special case for anonymous authentication: - if (message.LmChallengeResponse.Length == 1 || message.NtChallengeResponse.Length == 0) - { - return true; - } - - if ((message.NegotiateFlags & NegotiateFlags.ExtendedSecurity) > 0) - { - if (AuthenticationMessageUtils.IsNTLMv1ExtendedSecurity(message.LmChallengeResponse)) - { - // NTLM v1 extended security: - byte[] clientChallenge = ByteReader.ReadBytes(message.LmChallengeResponse, 0, 8); - byte[] emptyPasswordNTLMv1Response = NTLMCryptography.ComputeNTLMv1ExtendedSecurityResponse(m_serverChallenge, clientChallenge, String.Empty); - if (ByteUtils.AreByteArraysEqual(emptyPasswordNTLMv1Response, message.NtChallengeResponse)) - { - return true; - } - } - else - { - // NTLM v2: - byte[] _LMv2ClientChallenge = ByteReader.ReadBytes(message.LmChallengeResponse, 16, 8); - byte[] emptyPasswordLMv2Response = NTLMCryptography.ComputeLMv2Response(m_serverChallenge, _LMv2ClientChallenge, String.Empty, message.UserName, message.DomainName); - if (ByteUtils.AreByteArraysEqual(emptyPasswordLMv2Response, message.LmChallengeResponse)) - { - return true; - } - - if (AuthenticationMessageUtils.IsNTLMv2NTResponse(message.NtChallengeResponse)) - { - byte[] clientNTProof = ByteReader.ReadBytes(message.NtChallengeResponse, 0, 16); - byte[] clientChallengeStructurePadded = ByteReader.ReadBytes(message.NtChallengeResponse, 16, message.NtChallengeResponse.Length - 16); - byte[] emptyPasswordNTProof = NTLMCryptography.ComputeNTLMv2Proof(m_serverChallenge, clientChallengeStructurePadded, String.Empty, message.UserName, message.DomainName); - if (ByteUtils.AreByteArraysEqual(clientNTProof, emptyPasswordNTProof)) - { - return true; - } - } - } - } - else - { - // NTLM v1: - byte[] emptyPasswordLMv1Response = NTLMCryptography.ComputeLMv1Response(m_serverChallenge, String.Empty); - if (ByteUtils.AreByteArraysEqual(emptyPasswordLMv1Response, message.LmChallengeResponse)) - { - return true; - } - - byte[] emptyPasswordNTLMv1Response = NTLMCryptography.ComputeNTLMv1Response(m_serverChallenge, String.Empty); - if (ByteUtils.AreByteArraysEqual(emptyPasswordNTLMv1Response, message.NtChallengeResponse)) - { - return true; - } - } - - return false; - } - - public bool FallbackToGuest(string userName) - { - return (EnableGuestLogin && (IndexOf(userName) == -1)); - } - - /// - /// We immitate Windows, Guest logins are disabled in any of these cases: - /// 1. The Guest account is disabled. - /// 2. The Guest account has password set. - /// 3. The Guest account is listed in the "deny access to this computer from the network" list. - /// - private bool EnableGuestLogin - { - get - { - return LoginAPI.ValidateUserPassword("Guest", String.Empty, LogonType.Network); - } - } - - public static bool AreEmptyPasswordsAllowed() - { - RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Lsa"); - object value = key.GetValue("limitblankpassworduse", 1); - if (value is int) - { - if ((int)value != 0) - { - return false; - } - } - return true; - } - } -} diff --git a/SMBServer/SMBServer.csproj b/SMBServer/SMBServer.csproj index d39f1d9..d3d9f66 100644 --- a/SMBServer/SMBServer.csproj +++ b/SMBServer/SMBServer.csproj @@ -70,6 +70,8 @@ Settings.settings True + + diff --git a/SMBServer/ServerUI.cs b/SMBServer/ServerUI.cs index e2fa21f..80a4d2f 100644 --- a/SMBServer/ServerUI.cs +++ b/SMBServer/ServerUI.cs @@ -17,8 +17,9 @@ using System.Text; using System.Windows.Forms; using System.Xml; using SMBLibrary; +using SMBLibrary.Authentication.NTLM; using SMBLibrary.Server; -using SMBLibrary.Server.Win32; +using SMBLibrary.Win32.Security; using Utilities; namespace SMBServer @@ -61,10 +62,10 @@ namespace SMBServer transportType = SMBTransportType.DirectTCPTransport; } - INTLMAuthenticationProvider provider; + NTLMAuthenticationProviderBase provider; if (chkIntegratedWindowsAuthentication.Checked) { - provider = new Win32UserCollection(); + provider = new IntegratedNTLMAuthenticationProvider(); } else @@ -80,7 +81,7 @@ namespace SMBServer return; } - provider = new IndependentUserCollection(users); + provider = new IndependentNTLMAuthenticationProvider(users.GetUserPassword); } diff --git a/SMBLibrary/Server/User.cs b/SMBServer/User.cs similarity index 100% rename from SMBLibrary/Server/User.cs rename to SMBServer/User.cs diff --git a/SMBLibrary/Server/UserCollection.cs b/SMBServer/UserCollection.cs similarity index 80% rename from SMBLibrary/Server/UserCollection.cs rename to SMBServer/UserCollection.cs index 4ab94d9..b4568af 100644 --- a/SMBLibrary/Server/UserCollection.cs +++ b/SMBServer/UserCollection.cs @@ -29,6 +29,16 @@ namespace SMBLibrary.Server return -1; } + public string GetUserPassword(string accountName) + { + int index = IndexOf(accountName); + if (index >= 0) + { + return this[index].Password; + } + return null; + } + public List ListUsers() { List result = new List();