I am trying to enumerate a directory on a remote file server.
I want to use backup-semantics in order to not require administrator credentials.
On the test server, I have created a share:
Share permissions: everyone full control
NTFS permissions: only SYSTEM (I removed all others)
I am currently using this code:
static void accessWithBackupSemantics() {
NetResource netResource = new NetResource() {
Scope = ResourceScope.GlobalNetwork,
ResourceType = ResourceType.Disk,
DisplayType = ResourceDisplayType.Share,
Usage = ResourceUsage.Connectable,
RemoteName = #"\\target-srv\TargetShare"
};
// open "net use" connection
int netResult = Native.WNetAddConnection2(netResource,
#"***password***",
#"DOMAIN\backup_op_user",
0);
if (netResult == 0 || netResult == 1219) {
// enable privileges
// (this is taken from AplhaFS)
using (new PrivilegeEnabler(Privilege.Backup)) {
try {
// try open remote directory
SafeFileHandle fsHandle = Native.CreateFile(
#"\\target-srv\TargetShare",
EFileAccess.GenericRead,
EFileShare.Read | EFileShare.Write,
IntPtr.Zero,
ECreationDisposition.OpenExisting,
EFileAttributes.BackupSemantics,
IntPtr.Zero);
Console.WriteLine("Handle is valid: " + !fsHandle.IsInvalid);
}
catch (Exception ex) {}
finally {
Native.WNetCancelConnection2(netResource.RemoteName, 0, true);
}
}
}
}
PrivilegeEnabler class is taken from AlphaFS
Native win32 structures and flags are taken from pinvoke.net/kernel32.createfile
This works if I specify "DOMAIN\Administrator" in the username, but does not work (the error is 5 - access denied) if I try to use a domain account that is a member of the local "Backup Operators" on target-srv server.
I have also examined the security event log on target-srv, for every connection created with WNetAddConnection2 a "Special Logon" event is written. The details of this event include the list of privileges that the logon account was given.
In both cases (when I connect with administrator or with backup_op_user) - seBackupPrivilege is indeed listed.
I tried to give extra privileges to the "Backup Operators" so the list has all the privileges that the Administrator has - but it made no change.
Questions:
What is the right way to use Backup-Semantics over the network?
How come it works with Administrator and not with a member of "Backup Operators" - are there additional implicit permissions for the Admin?
I have seen many examples of local use of Backup-Semantics, but not one that can be used over the network - please don't reply with links to examples of local usage.
Related
TL;DR: What Database.FileFormat constant should I use for an MS Access 2000-2003 database, when creating the Database object?
I have built a SAMBA test application using jCIFS. It allows me to create/overwrite files if given the correct authentication credentials, regardless of on which PC in the domain I use it.
I also have an application that uses uCanAccess/jackcess to connect to an MDB on a network share. However (from what I understand), it uses the credentials of the logged-in user, a number of whom have read-only access. Only system/network administrators have write permission.
The database in question is not password-protected. (I don't need to enter a password when opening it.)
My intention is to have the app ask for the administrator's Samba credentials before it writes to the DB, using those in the uCanAccess connection, so that it doesn't throw a java.nio.channels.NonWritableChannelException, as per the below stack trace:
java.nio.channels.NonWritableChannelException
at sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:747)
at com.healthmarketscience.jackcess.impl.PageChannel.writePage(PageChannel.java:310)
at com.healthmarketscience.jackcess.impl.PageChannel.writePage(PageChannel.java:247)
at com.healthmarketscience.jackcess.impl.TableImpl.writeDataPage(TableImpl.java:1980)
at com.healthmarketscience.jackcess.impl.TableImpl.addRows(TableImpl.java:2229)
at com.healthmarketscience.jackcess.impl.TableImpl.addRow(TableImpl.java:2067)
at net.ucanaccess.converters.UcanaccessTable.addRow(UcanaccessTable.java:44)
at net.ucanaccess.commands.InsertCommand.insertRow(InsertCommand.java:101)
at net.ucanaccess.commands.InsertCommand.persist(InsertCommand.java:148)
at net.ucanaccess.jdbc.UcanaccessConnection.flushIO(UcanaccessConnection.java:315)
at net.ucanaccess.jdbc.UcanaccessConnection.commit(UcanaccessConnection.java:205)
at net.ucanaccess.jdbc.AbstractExecute.executeBase(AbstractExecute.java:217)
at net.ucanaccess.jdbc.Execute.execute(Execute.java:46)
at net.ucanaccess.jdbc.UcanaccessPreparedStatement.execute(UcanaccessPreparedStatement.java:228)
at myapp.db.Digger.addTransaction(Digger.java:993)
at myapp.tasks.TransactionRunnable.run(TransactionRunnable.java:42)
at java.lang.Thread.run(Thread.java:745)
Update: I have tried using the smbFileChannel class by Gord Thompson and J. T. Alhborn, shown here. My code, based off the main class shown in that answer, looks like this:
// Ask the user for login credentials and the path to the database
String smbURL = (chosenDir.endsWith("/") ? chosenDir : chosenDir + '/')
+ dbName;
System.out.println("DB Path to use for URL: " + smbURL);
URL u = new URL(smbURL);
try (
// construct the SMB DB URL
SmbFileChannel sfc = new SmbFileChannel(smbURL);
Database db = new DatabaseBuilder().setChannel(sfc)
.setFileFormat(Database.FileFormat.GENERIC_JET4).create();
) {
// Model the table
Table tbl = new TableBuilder("Transactions")
.addColumn(new ColumnBuilder("TransactionID", DataType.LONG).setAutoNumber(true))
.addColumn(new ColumnBuilder("ControllerID", DataType.LONG).setAutoNumber(false))
.addColumn(new ColumnBuilder("ReaderID", DataType.LONG).setAutoNumber(false))
.addColumn(new ColumnBuilder("Event", DataType.LONG).setAutoNumber(false))
.addColumn(new ColumnBuilder("Timestamp", DataType.SHORT_DATE_TIME).setAutoNumber(false))
.addColumn(new ColumnBuilder("Number", DataType.LONG).setAutoNumber(false))
.addIndex(new IndexBuilder(IndexBuilder.PRIMARY_KEY_NAME).addColumns("TransactionID").setPrimaryKey())
.toTable(db);
// Add the row
Map<String, Object> values = new HashMap<>();
values.put("ControllerID", cid);
values.put("ReaderID", rid);
values.put("Event", evtNum);
values.put("Timestamp", ts); // Long; must be converted to DataType.SHORT_DATE_TIME
values.put("Number", accNum);
tbl.addRowFromMap(values);
} catch (IOException IOEx) {
System.err.println(
"Failed to write record to Transactions table in database: "
+ IOEx.getMessage()
);
IOEx.printStackTrace(System.err);
} catch (Exception ex) {
System.err.println(
'[' + ex.getClass().getSimpleName() + "]: Failed to write record to "
+ "Transactions table in database: " + ex.getMessage()
);
ex.printStackTrace(System.err);
}
Executing the above code results in the following output:
DB Path to use for URL: smb://machine.vpnName/Storage/me/dbs/DBName.mdb
Failed to write record to Transactions table in database: Logon failure: account currently disabled.
jcifs.smb.SmbAuthException: Logon failure: account currently disabled.
at jcifs.smb.SmbTransport.checkStatus(SmbTransport.java:546)
at jcifs.smb.SmbTransport.send(SmbTransport.java:663)
at jcifs.smb.SmbSession.sessionSetup(SmbSession.java:390)
at jcifs.smb.SmbSession.send(SmbSession.java:218)
at jcifs.smb.SmbTree.treeConnect(SmbTree.java:176)
at jcifs.smb.SmbFile.doConnect(SmbFile.java:911)
at jcifs.smb.SmbFile.connect(SmbFile.java:957)
at jcifs.smb.SmbFile.connect0(SmbFile.java:880)
at jcifs.smb.SmbFile.open0(SmbFile.java:975)
at jcifs.smb.SmbFile.open(SmbFile.java:1009)
at jcifs.smb.SmbRandomAccessFile.<init>(SmbRandomAccessFile.java:57)
at jcifs.smb.SmbRandomAccessFile.<init>(SmbRandomAccessFile.java:42)
at samba.SmbFileChannel.<init>(SmbFileChannel.java:30)
at samba.SambaLanWriteTest.writeTest(SambaLanWriteTest.java:130)
at samba.SambaLanWriteTest.main(SambaLanWriteTest.java:181)
I have write access to a test copy of the database file in question when using Windows File Explorer. I am choosing that one when prompted.
Update 2: I realised that I neglected to add my username and password to the smb:// URL, as Thompson's example shows. I changed to code to this:
String smbCred = "smb://" + auth.getUsername() + ":" + auth.getPassword() + "#",
fixer = chosenDir.replace("\\", "/").replace("smb://", smbCred),
smbURL = fixer + dbName;
System.out.println("DB Path to use for URL: " + smbURL);
// URL u = new URL(smbURL);
The next problem I had was that my password contains special illegal characters (such as '#', ':', ';', '=' and '?'). I escaped these by using java.net.URLEncoder.encode() on auth.getUsername() and auth.getPassword() so the code doesn't throw a MalformedURLException when creating the SmbChannel. However, the next exception I encountered is as follows:
Failed to write record to Transactions table in database: File format GENERIC_JET4 [VERSION_4] does not support file creation for null
java.io.IOException: File format GENERIC_JET4 [VERSION_4] does not support file creation for null
at com.healthmarketscience.jackcess.impl.DatabaseImpl.create(DatabaseImpl.java:444)
What Database.FileFormat constant should I use for an MS Access 2000-2003 database, when creating the Database object?
It turns out that I needed to use Database.FileFormat.V2000.
After that, it was all plain sailing (although I still need to work out how to get the Long timestamp to convert correctly).
I want to encrypt a file/folder using EncryptFile function while Impersonating another user, I gave the other user Full Control over the file, but I still get Access Denied.
HANDLE hUser;
if (LogonUser(L"test", L".", L"123", LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, &hUser) == TRUE) {
ImpersonateLoggedOnUser(hUser);
if (EncryptFile(dir_to_enc) == FALSE) {
printf("%d\n", GetLastError()); // I get 5 - Access Denied
}
RevertToSelf();
CloseHandle(hUser);
}
edit:
when I change the fourth parameter of LogonUser from LOGON32_LOGON_NETWORK to LOGON32_LOGON_INTERACTIVE, the error code changes to 87, ERROR_INVALID_PARAMETER.
I don't know why but when I change the dwLogonType to LOGON32_LOGON_BATCH everything works correctly!
It seems that it is used when we need to do something on behalf of the user.
but other types are not directly intended for that.
I'm trying to implement rebooting of a remote computer with InitiateShutdown API using the following code, but it fails with RPC_S_SERVER_UNAVAILABLE or 1722 error code:
//Process is running as administrator
//Select a remote machine to reboot:
//INFO: Tried it with and w/o two opening slashes.
LPCTSTR pServerName = L"192.168.42.105";
//Or use 127.0.0.1 if you don't have access to another machine on your network.
//This will attempt to reboot your local machine.
//In that case make sure to call shutdown /a /m \\127.0.0.1 to cancel it.
if(AdjustPrivilege(NULL, L"SeShutdownPrivilege", TRUE) &&
AdjustPrivilege(pServerName, L"SeRemoteShutdownPrivilege", TRUE))
{
int nErrorCode = ::InitiateShutdown(pServerName, NULL, 30,
SHUTDOWN_INSTALL_UPDATES | SHUTDOWN_RESTART, 0);
//Receive nErrorCode == 1722, or RPC_S_SERVER_UNAVAILABLE
}
BOOL AdjustPrivilege(LPCTSTR pStrMachine, LPCTSTR pPrivilegeName, BOOL bEnable)
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
BOOL bRes = FALSE;
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return FALSE;
if(LookupPrivilegeValue(pStrMachine, pPrivilegeName, &tkp.Privileges[0].Luid))
{
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : SE_PRIVILEGE_REMOVED;
bRes = AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
int nOSError = GetLastError();
if(bRes)
{
if(nOSError != ERROR_SUCCESS)
bRes = FALSE;
}
}
CloseHandle(hToken);
return bRes;
}
So to prepare for this code to run I do the following on this computer, which is Windows 7 Pro (as I would do for the Microsoft's shutdown tool):
Run the following "as administrator" to allow SMB access to the logged in user D1 on the 192.168.42.105 computer (per this answer):
NET USE \\192.168.42.105\IPC$ 1234 /USER:D1
Run the process with my code above "as administrator".
And then do the following on remote computer, or 192.168.42.105, that has Windows 7 Pro (per answer here with most upvotes):
Control Panel, Network and Sharing Center, Change Advanced Sharing settings
"Private" enable "Turn on File and Printer sharing"
Set the following key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
LocalAccountTokenFilterPolicy=dword:1
RUN secpol.msc, then go to Local Security Policy, Security Settings, Local Policies, User Rights Assignment. Add "Everyone" to "Force shutdown from a remote system". (Just remember to remove it after you're done testing!)
Note that the following shutdown command seems to work just fine to reboot the remote computer:
shutdown /r /m \\192.168.42.105 /t 30
What am I missing with my code?
EDIT:
OK. I will admit that I was merely interested in why InitiateShutdown doesn't seem to "want" to work with a remote server connection, while InitiateSystemShutdownEx or InitiateSystemShutdown had no issues at all. (Unfortunately the latter two did not have the dwShutdownFlags parameter, which I needed to pass the SHUTDOWN_INSTALL_UPDATES flag to, which caused my persistence...)
At this point I had no other way of finding out than dusting out a copy of WinDbg... I'm still trying to dig into it, but so far this is what I found...
(A) It turns out that InitiateSystemShutdownEx internally uses a totally different RPC call. W/o too many details, it initiates RPC binding with RpcStringBindingComposeW using the following parameters:
ObjUuid = NULL
ProtSeq = ncacn_np
NetworkAddr = \\192.168.42.105
EndPoint = \\PIPE\\InitShutdown
Options = NULL
or the following binding string:
ncacn_np:\\\\192.168.42.105[\\PIPE\\InitShutdown]
(B) While InitiateShutdown on the other hand uses the following binding parameters:
ObjUuid = 765294ba-60bc-48b8-92e9-89fd77769d91
ProtSeq = ncacn_ip_tcp
NetworkAddr = 192.168.42.105
EndPoint = NULL
Options = NULL
which it later translates into the following binding string:
ncacn_np:\\\\192.168.42.105[\\PIPE\\lsarpc]
that it uses to obtain the RPC handle that it passes to WsdrInitiateShutdown (that seems to have its own specification):
So as you see, the InitiateShutdown call is technically treated as Unknown RPC service (for the UUID {765294ba-60bc-48b8-92e9-89fd77769d91}), which later causes a whole bunch of credential checks between the server and the client:
which, honestly, I'm not sure I want to step into with a low-level debugger :)
At this stage I will say that I am not very well versed on "Local Security Authority" interface (or the \PIPE\lsarpc named pipe configuration.) So if anyone knows what configuration is missing on the server side to allow this RPC call to go through, I would appreciate if you could post your take on it?
I need to know if a specified directory (local or shared path with login credentials) has write permissions or not.
I am using GetFileAttributes but it always returns FILE_ATTRIBUTE_DIRECTORY and nothing else.
My code is something like below
if(storageLocation != "")
{
//! check if local storage - user name password would be empty
if(storageUsername == "" && storagePassword == "")
{
//! local storage
//! lets check whether the local path is a valid path or not
boost::filesystem::path fpath(storageUsername.c_str());
if(boost::filesystem::exists(fpath))
{
DWORD attrib = ::GetFileAttributes(storageLocation.c_str());
if((attrib != INVALID_FILE_ATTRIBUTES) &&
((attrib & FILE_ATTRIBUTE_READONLY) != FILE_ATTRIBUTE_READONLY))
{
string strWritePermission = "TRUE";
}
}
}
else
{
uncLocation_t uncLocation;
uncLocation.m_location = storageLocation;
uncLocation.m_username = storageUsername;
uncLocation.m_password = storagePassword;
if(0 == connectToUNCLocation(uncLocation)) // My function to connect to UNC location
{
//! successful connection
DWORD attrib = ::GetFileAttributes(storageLocation.c_str());
if((attrib != INVALID_FILE_ATTRIBUTES) &&
((attrib & FILE_ATTRIBUTE_READONLY) != FILE_ATTRIBUTE_READONLY))
{
string strWritePermission = "TRUE";
}
}
}
}
I don't understand why but GetFileAttributes always return 0x16.
I have tested it by creating a shared folder and creating 2 folders in it. One with read only permissions and other with default permissions. But in all 3 cases (shared folder, read only folder and default permission folder) I am getting same return value.
There is on way to find write permission, to create a temporary file (usinf CreateFile in GENERIC_WRITE mode) and if successfully created, delete it. But I don't want to use this method as I don't want my application to create a temporary file each time user specifies a location.
Please suggest what should be done.
You would need to replicate the security checking that Windows performs. The AccessCheck function will help that. You are currently well wide of the mark in looking at the file attributes. Windows security is so much more complicated than that.
Although you said you did not want to do it, the right solution is not to try to check. Simply do whatever it is you are attempting to do. If the system decides that the user does not have sufficient rights, then CreateFile will fail, and the last error will be set to ERROR_ACCESS_DENIED. There's no need for temporary files. You just try to do whatever it is you are doing, and let it fail. You have to handle failure anyway since there are many ways for a file operation to fail, not just security.
As the saying goes, it is better to ask forgiveness than permission.
I think you are looking for AccessCheck. FYI, this is not a C++ question, but a Windows API question.
It has been asked, and answered for .NET, but now it's time to get an answer for native Win32 code:
How do i validate a Windows username and password?
i asked this question before for managed code. Now it's time for the native solution.
It needs to be pointed the pitfalls with some of the more commonly proposed solutions:
Invalid Method 1. Query Active Directory with Impersonation
A lot of people suggest querying the Active Directory for something. If an exception is thrown, then you know the credentials are not valid - as is suggested in this stackoverflow question.
There are some serious drawbacks to this approach however:
You are not only authenticating a domain account, but you are also doing an implicit authorization check. That is, you are reading properties from the AD using an impersonation token. What if the otherwise valid account has no rights to read from the AD? By default all users have read access, but domain policies can be set to disable access permissions for restricted accounts (and or groups).
Binding against the AD has a serious overhead, the AD schema cache has to be loaded at the client (ADSI cache in the ADSI provider used by DirectoryServices). This is both network, and AD server, resource consuming - and is too expensive for a simple operation like authenticating a user account.
You're relying on an exception failure for a non-exceptional case, and assuming that means invalid username and password. Other problems (e.g. network failure, AD connectivity failure, memory allocation error, etc) are then mis-intrepreted as authentication failure.
The use of the DirectoryEntry class is .NET is an example of an incorrect way to verify credentials:
Invalid Method 1a - .NET
DirectoryEntry entry = new DirectoryEntry("persuis", "iboyd", "Tr0ub4dor&3");
object nativeObject = entry.NativeObject;
Invalid Method 1b - .NET #2
public static Boolean CheckADUserCredentials(String accountName, String password, String domain)
{
Boolean result;
using (DirectoryEntry entry = new DirectoryEntry("LDAP://" + domain, accountName, password))
{
using (DirectorySearcher searcher = new DirectorySearcher(entry))
{
String filter = String.Format("(&(objectCategory=user)(sAMAccountName={0}))", accountName);
searcher.Filter = filter;
try
{
SearchResult adsSearchResult = searcher.FindOne();
result = true;
}
catch (DirectoryServicesCOMException ex)
{
const int SEC_E_LOGON_DENIED = -2146893044; //0x8009030C;
if (ex.ExtendedError == SEC_E_LOGON_DENIED)
{
// Failed to authenticate.
result = false;
}
else
{
throw;
}
}
}
}
As well as querying Active Directory through an ADO connection:
Invalid Method 1c - Native Query
connectionString = "Provider=ADsDSOObject;
User ID=iboyd;Password=Tr0ub4dor&3;
Encrypt Password=True;Mode=Read;
Bind Flags=0;ADSI Flag=-2147483648';"
SELECT userAccountControl
FROM 'LDAP://persuis/DC=stackoverflow,DC=com'
WHERE objectClass='user' and sAMAccountName = 'iboyd'
These both fail even when your credentials are valid, but you do not have permission to view your directory entry:
Invalid Method 2. LogonUser Win32 API
Others have suggested using the LogonUser() API function. This sounds nice, but unfortunatly the calling user sometimes needs a permission ususally only given to the operating system itself:
The process calling LogonUser requires
the SE_TCB_NAME privilege. If the
calling process does not have this
privilege, LogonUser fails and
GetLastError returns
ERROR_PRIVILEGE_NOT_HELD.
In some
cases, the process that calls
LogonUser must also have the
SE_CHANGE_NOTIFY_NAME privilege
enabled; otherwise, LogonUser fails
and GetLastError returns
ERROR_ACCESS_DENIED. This privilege is
not required for the local system
account or accounts that are members
of the administrators group. By
default, SE_CHANGE_NOTIFY_NAME is
enabled for all users, but some
administrators may disable it for
everyone.
Handing out the "Act as a part of the operating system" privelage is not something you want to do willy-nilly - as Microsoft points out in a knowledge base article:
...the process that is calling
LogonUser must have the SE_TCB_NAME
privilege (in User Manager, this is
the "Act as part of the Operating
System" right). The SE_TCB_NAME
privilege is very powerful and
should not be granted to any arbitrary user just so that they can
run an application that needs to
validate credentials.
Additionally, a call to LogonUser() will fail if a blank password is specified.
Valid .NET 3.5 Method - PrincipalContext
There is a validation method, only available in .NET 3.5 and newer, that allows authentication by a user without performing an authorization check:
// create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "stackoverflow.com"))
{
// validate the credentials
bool isValid = pc.ValidateCredentials("iboyd", "Tr0ub4dor&3")
}
Unfortunately this code is only available in .NET 3.5 and later.
It's time to find the native equivalent.
Here is Microsoft's recommendation.
As for the other answers, I'm not really sure why you're shooting them down. You are complaining about (relatively edge case) failures while trying to validate credentials, but if you are going to actually do something with those credentials then that operation is just going to fail anyway. If you are not going to actually do something with those credentials, then why do you need to validate them in the first place? It seems like a somewhat contrived situation, but obviously I don't know what you're trying to accomplish.
For the native equivalnt of your valid .NET solution see this MSDN page and ldap_bind
Howerver I think that LogonUser is the right API for the task when use with LOGON32_LOGON_NETWORK. Note that the limitation of SE_CHANGE_NOTIFY_NAME is only for Windows 2000 (so Windows XP and newer do not require this priviledge) and that by default SE_CHANGE_NOTIFY_NAME is enabled for all users. Also the MSDN page says
The SE_TCB_NAME privilege is not required for this function unless you are logging onto a Passport account.
In this case you are logging onto an AD account so SE_TCB_NAME is not required.
I might as well post the native code to validate a set of Windows credentials. It took a while to implement.
function TSSPLogon.LogonUser(username, password, domain: string; packageName: string='Negotiate'): HRESULT;
var
ss: SECURITY_STATUS;
packageInfo: PSecPkgInfoA;
cbMaxToken: DWORD;
clientBuf: PByte;
serverBuf: PByte;
authIdentity: SEC_WINNT_AUTH_IDENTITY;
cbOut, cbIn: DWORD;
asClient: AUTH_SEQ;
asServer: AUTH_SEQ;
Done: boolean;
begin
{
If domain is blank will use the current domain.
To force validation against the local database use domain "."
sspiProviderName is the same of the Security Support Provider Package to use. Some possible choices are:
- Negotiate (Preferred)
Introduced in Windows 2000 (secur32.dll)
Selects Kerberos and if not available, NTLM protocol.
Negotiate SSP provides single sign-on capability called as Integrated Windows Authentication.
On Windows 7 and later, NEGOExts is introduced which negotiates the use of installed
custom SSPs which are supported on the client and server for authentication.
- Kerberos
Introduced in Windows 2000 and updated in Windows Vista to support AES) (secur32.dll)
Preferred for mutual client-server domain authentication in Windows 2000 and later.
- NTLM
Introduced in Windows NT 3.51 (Msv1_0.dll)
Provides NTLM challenge/response authentication for client-server domains prior to
Windows 2000 and for non-domain authentication (SMB/CIFS)
- Digest
Introduced in Windows XP (wdigest.dll)
Provides challenge/response based HTTP and SASL authentication between Windows and non-Windows systems where Kerberos is not available
- CredSSP
Introduced in Windows Vista and available on Windows XP SP3 (credssp.dll)
Provides SSO and Network Level Authentication for Remote Desktop Services
- Schannel
Introduced in Windows 2000 and updated in Windows Vista to support stronger AES encryption and ECC (schannel.dll)
Microsoft's implementation of TLS/SSL
Public key cryptography SSP that provides encryption and secure communication for
authenticating clients and servers over the internet. Updated in Windows 7 to support TLS 1.2.
If returns false, you can call GetLastError to get the reason for the failure
}
// Get the maximum authentication token size for this package
ss := sspi.QuerySecurityPackageInfoA(PAnsiChar(packageName), packageInfo);
if ss <> SEC_E_OK then
begin
RaiseWin32Error('QuerySecurityPackageInfo "'+PackageName+'" failed', ss);
Result := ss;
Exit;
end;
try
cbMaxToken := packageInfo.cbMaxToken;
finally
FreeContextBuffer(packageInfo);
end;
// Initialize authorization identity structure
ZeroMemory(#authIdentity, SizeOf(authIdentity));
if Length(domain) > 0 then
begin
authIdentity.Domain := PChar(Domain);
authIdentity.DomainLength := Length(domain);
end;
if Length(userName) > 0 then
begin
authIdentity.User := PChar(UserName);
authIdentity.UserLength := Length(UserName);
end;
if Length(Password) > 0 then
begin
authIdentity.Password := PChar(Password);
authIdentity.PasswordLength := Length(Password);
end;
AuthIdentity.Flags := SEC_WINNT_AUTH_IDENTITY_ANSI; //SEC_WINNT_AUTH_IDENTITY_UNICODE
ZeroMemory(#asClient, SizeOf(asClient));
ZeroMemory(#asServer, SizeOf(asServer));
//Allocate buffers for client and server messages
GetMem(clientBuf, cbMaxToken);
GetMem(serverBuf, cbMaxToken);
try
done := False;
try
// Prepare client message (negotiate)
cbOut := cbMaxToken;
ss := Self.GenClientContext(#asClient, authIdentity, packageName, nil, 0, clientBuf, cbOut, done);
if ss < 0 then
begin
RaiseWin32Error('Error generating client context for negotiate', ss);
Result := ss;
Exit;
end;
// Prepare server message (challenge).
cbIn := cbOut;
cbOut := cbMaxToken;
ss := Self.GenServerContext(#asServer, packageName, clientBuf, cbIn, serverBuf, cbOut, done);
if ss < 0 then
begin
{
Most likely failure: AcceptServerContext fails with SEC_E_LOGON_DENIED in the case of bad username or password.
Unexpected Result: Logon will succeed if you pass in a bad username and the guest account is enabled in the specified domain.
}
RaiseWin32Error('Error generating server message for challenge', ss);
Result := ss;
Exit;
end;
// Prepare client message (authenticate).
cbIn := cbOut;
cbOut := cbMaxToken;
ss := Self.GenClientContext(#asClient, authIdentity, packageName, serverBuf, cbIn, clientBuf, cbOut, done);
if ss < 0 then
begin
RaiseWin32Error('Error generating client client for authenticate', ss);
Result := ss;
Exit;
end;
// Prepare server message (authentication).
cbIn := cbOut;
cbOut := cbMaxToken;
ss := Self.GenServerContext(#asServer, packageName, clientBuf, cbIn, serverBuf, cbOut, done);
if ss < 0 then
begin
RaiseWin32Error('Error generating server message for authentication', ss);
Result := ss;
Exit;
end;
finally
//Free resources in client message
if asClient.fHaveCtxtHandle then
sspi.DeleteSecurityContext(#asClient.hctxt);
if asClient.fHaveCredHandle then
sspi.FreeCredentialHandle(#asClient.hcred);
//Free resources in server message
if asServer.fHaveCtxtHandle then
sspi.DeleteSecurityContext(#asServer.hctxt);
if asServer.fHaveCredHandle then
sspi.FreeCredentialHandle(#asServer.hcred);
end;
finally
FreeMem(clientBuf);
FreeMem(serverBuf);
end;
Result := S_OK;
end;
Note: Any code released into public domain. No attribution required.
There is a win32 API function called ldap_bind_s. The ldap_bind_s function authenticates a client
against LDAP. See MSDN documentation for more information.
I authenticated user, by username & password like this :
username is user sn attribute value in Ldap server, like U12345
userDN is user DistinguishedName in LdapServer
public bool AuthenticateUser(string username, string password)
{
try
{
var ldapServerNameAndPort = "Servername:389";
var userDN = string.Format("CN=0},OU=Users,OU=MyOU,DC=MyDC,DC=com",username);
var conn = new LdapConnection(ldapServerNameAndPort)
{
AuthType = AuthType.Basic
};
conn.Bind(new NetworkCredential(userDN , password));
return true;
}
catch (Exception e)
{
return false;
}
}