Parcourir la source

Improved NTLM authentication API

Tal Aloni il y a 8 ans
Parent
commit
217451d18f

+ 18 - 0
SMBLibrary/Authentication/GSSAPI/Enums/GSSAttributeName.cs

@@ -0,0 +1,18 @@
+using System;
+
+namespace SMBLibrary.Authentication.GSSAPI
+{
+    public enum GSSAttributeName
+    {
+        AccessToken,
+        IsAnonymous,
+        
+        /// <summary>
+        /// Permit access to this user via the guest user account if the normal authentication process fails.
+        /// </summary>
+        IsGuest,
+        MachineName,
+        SessionKey,
+        UserName,
+    }
+}

+ 236 - 0
SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs

@@ -0,0 +1,236 @@
+/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. 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;
+
+        /// <param name="getUserPassword">
+        /// The NTLM challenge response will be compared against the provided password.
+        /// </param>
+        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);
+            }
+        }
+
+        /// <summary>
+        /// LM v1 / NTLM v1
+        /// </summary>
+        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);
+        }
+
+        /// <summary>
+        /// LM v1 / NTLM v1 Extended Security
+        /// </summary>
+        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);
+        }
+
+        /// <summary>
+        /// LM v2 / NTLM v2
+        /// </summary>
+        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;
+        }
+
+        /// <summary>
+        /// Generate 8-byte server challenge
+        /// </summary>
+        private static byte[] GenerateServerChallenge()
+        {
+            byte[] serverChallenge = new byte[8];
+            new Random().NextBytes(serverChallenge);
+            return serverChallenge;
+        }
+    }
+}

+ 23 - 0
SMBLibrary/Authentication/NTLM/NTLMAuthenticationProviderBase.cs

@@ -0,0 +1,23 @@
+/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. 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);
+    }
+}

+ 8 - 0
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

+ 7 - 0
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,
     }
 }

+ 5 - 6
SMBLibrary/SMBLibrary.csproj

@@ -31,6 +31,7 @@
     <Reference Include="System" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Authentication\GSSAPI\Enums\GSSAttributeName.cs" />
     <Compile Include="Authentication\GSSAPI\GSSAPIHelper.cs" />
     <Compile Include="Authentication\GSSAPI\SPNEGO\DerEncodingHelper.cs" />
     <Compile Include="Authentication\GSSAPI\SPNEGO\SimpleProtectedNegotiationToken.cs" />
@@ -40,6 +41,8 @@
     <Compile Include="Authentication\NTLM\Helpers\AVPairUtils.cs" />
     <Compile Include="Authentication\NTLM\Helpers\MD4.cs" />
     <Compile Include="Authentication\NTLM\Helpers\NTLMCryptography.cs" />
+    <Compile Include="Authentication\NTLM\IndependentNTLMAuthenticationProvider.cs" />
+    <Compile Include="Authentication\NTLM\NTLMAuthenticationProviderBase.cs" />
     <Compile Include="Authentication\NTLM\Structures\AuthenticateMessage.cs" />
     <Compile Include="Authentication\NTLM\Structures\ChallengeMessage.cs" />
     <Compile Include="Authentication\NTLM\Structures\Enums\AVPairKey.cs" />
@@ -181,12 +184,10 @@
     <Compile Include="Server\ConnectionState\SMB1Session.cs" />
     <Compile Include="Server\ConnectionState\SMB2ConnectionState.cs" />
     <Compile Include="Server\ConnectionState\SMB2Session.cs" />
-    <Compile Include="Server\Exceptions\EmptyPasswordNotAllowedException.cs" />
     <Compile Include="Server\Exceptions\InvalidRequestException.cs" />
     <Compile Include="Server\Exceptions\UnsupportedInformationLevelException.cs" />
+    <Compile Include="Server\Helpers\LogonHelper.cs" />
     <Compile Include="Server\Helpers\ServerPathUtils.cs" />
-    <Compile Include="Server\IndependentUserCollection.cs" />
-    <Compile Include="Server\INTLMAuthenticationProvider.cs" />
     <Compile Include="Server\NameServer.cs" />
     <Compile Include="Server\Shares\FileSystemShare.cs" />
     <Compile Include="Server\Shares\ISMBShare.cs" />
@@ -222,8 +223,6 @@
     <Compile Include="Server\SMBServer.cs" />
     <Compile Include="Server\SMBServer.SMB1.cs" />
     <Compile Include="Server\SMBServer.SMB2.cs" />
-    <Compile Include="Server\User.cs" />
-    <Compile Include="Server\UserCollection.cs" />
     <Compile Include="Services\Enums\PlatformName.cs" />
     <Compile Include="Services\RemoteService.cs" />
     <Compile Include="Services\RemoteServiceHelper.cs" />
@@ -532,12 +531,12 @@
     <Compile Include="Utilities\LogEntry.cs" />
     <Compile Include="Utilities\PrefetchedStream.cs" />
     <Compile Include="Utilities\SocketUtils.cs" />
+    <Compile Include="Win32\IntegratedNTLMAuthenticationProvider.cs" />
     <Compile Include="Win32\Security\LoginAPI.cs" />
     <Compile Include="Win32\Security\NetworkAPI.cs" />
     <Compile Include="Win32\Security\SSPIHelper.cs" />
     <Compile Include="Win32\Security\Structures\SecBuffer.cs" />
     <Compile Include="Win32\Security\Structures\SecBufferDesc.cs" />
-    <Compile Include="Win32\Win32UserCollection.cs" />
   </ItemGroup>
   <ItemGroup>
     <Content Include="Readme.txt" />

+ 1 - 0
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)
         {

+ 0 - 23
SMBLibrary/Server/Exceptions/EmptyPasswordNotAllowedException.cs

@@ -1,23 +0,0 @@
-/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. 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)
-        {
-        }
-    }
-}

+ 42 - 0
SMBLibrary/Server/Helpers/LogonHelper.cs

@@ -0,0 +1,42 @@
+/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. 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;
+            }
+        }
+    }
+}

+ 0 - 33
SMBLibrary/Server/INTLMAuthenticationProvider.cs

@@ -1,33 +0,0 @@
-/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. 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);
-
-        /// <summary>
-        /// Permit access to this user via the guest user account if the normal authentication process fails.
-        /// </summary>
-        /// <remarks>
-        /// 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).
-        /// </remarks>
-        bool FallbackToGuest(string userName);
-    }
-}

+ 0 - 206
SMBLibrary/Server/IndependentUserCollection.cs

@@ -1,206 +0,0 @@
-/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. 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);
-        }
-
-        /// <summary>
-        /// LM v1 / NTLM v1
-        /// </summary>
-        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;
-        }
-
-        /// <summary>
-        /// LM v1 / NTLM v1 Extended Security
-        /// </summary>
-        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;
-        }
-
-        /// <summary>
-        /// LM v2 / NTLM v2
-        /// </summary>
-        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;
-            }
-        }
-    }
-}

+ 8 - 3
SMBLibrary/Server/SMB1/NegotiateHelper.cs

@@ -19,7 +19,7 @@ namespace SMBLibrary.Server.SMB1
     /// </summary>
     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;
 

+ 33 - 48
SMBLibrary/Server/SMB1/SessionSetupHelper.cs

@@ -19,56 +19,43 @@ namespace SMBLibrary.Server.SMB1
     /// </summary>
     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;
+                session = state.CreateSession(message.UserName, message.WorkStation);
             }
-            else if (users.FallbackToGuest(message.UserName))
+            else
             {
                 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;
+                session = state.CreateSession("Guest", message.WorkStation);
                 response.Action = SessionSetupAction.SetupGuest;
-                response.PrimaryDomain = request.PrimaryDomain;
             }
-            else
+
+            if (session == null)
             {
-                state.LogToServer(Severity.Information, "User '{0}' failed authentication", message.UserName);
-                header.Status = NTStatus.STATUS_LOGON_FAILURE;
+                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
-                {
-                    loginSuccess = users.Authenticate(authenticateMessage);
-                }
-                catch (EmptyPasswordNotAllowedException)
+                Win32Error loginStatus = securityProvider.Authenticate(state.AuthenticationContext, authenticateMessage);
+                if (loginStatus != Win32Error.ERROR_SUCCESS)
                 {
-                    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)
                 {

+ 16 - 17
SMBLibrary/Server/SMB2/SessionSetupHelper.cs

@@ -18,7 +18,7 @@ namespace SMBLibrary.Server.SMB2
     /// </summary>
     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
-                {
-                    loginSuccess = users.Authenticate(authenticateMessage);
-                }
-                catch (EmptyPasswordNotAllowedException)
+                Win32Error loginStatus = securityProvider.Authenticate(state.AuthenticationContext, authenticateMessage);
+                if (loginStatus != Win32Error.ERROR_SUCCESS)
                 {
-                    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)
                 {

+ 3 - 3
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)
             {

+ 1 - 1
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)
             {

+ 5 - 4
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<LogEntry> 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;

+ 176 - 0
SMBLibrary/Win32/IntegratedNTLMAuthenticationProvider.cs

@@ -0,0 +1,176 @@
+/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. 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;
+        }
+
+        /// <summary>
+        /// 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.
+        /// </summary>
+        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;
+        }
+
+        /// <summary>
+        /// 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.
+        /// </summary>
+        private bool EnableGuestLogin
+        {
+            get
+            {
+                return LoginAPI.ValidateUserPassword("Guest", String.Empty, LogonType.Network);
+            }
+        }
+
+        public static bool IsUserExists(string userName)
+        {
+            return NetworkAPI.IsUserExists(userName);
+        }
+    }
+}

+ 0 - 175
SMBLibrary/Win32/Win32UserCollection.cs

@@ -1,175 +0,0 @@
-/* Copyright (C) 2014-2017 Tal Aloni <tal.aloni.il@gmail.com>. 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<string> 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;
-        }
-
-        /// <summary>
-        /// 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.
-        /// </summary>
-        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));
-        }
-
-        /// <summary>
-        /// 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.
-        /// </summary>
-        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;
-        }
-    }
-}

+ 2 - 0
SMBServer/SMBServer.csproj

@@ -70,6 +70,8 @@
       <DependentUpon>Settings.settings</DependentUpon>
       <DesignTimeSharedInput>True</DesignTimeSharedInput>
     </Compile>
+    <Compile Include="User.cs" />
+    <Compile Include="UserCollection.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\SMBLibrary\SMBLibrary.csproj">

+ 5 - 4
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);
             }
 
 

SMBLibrary/Server/User.cs → SMBServer/User.cs


+ 10 - 0
SMBLibrary/Server/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<string> ListUsers()
         {
             List<string> result = new List<string>();