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();