Is it possible to use "Transfer-Encoding: Chunked" header with windows authentication? - winapi

WinHTTP authentication described here:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa383144(v=vs.85).aspx
works if I don't use "Transfer-Encoding: Chunked\r\n" header when calling WinHttpSendRequest().
If I do, I'm not able to log on, because WinHttpReceiveResponse() fails after setting credentials with WinHttpSetCredentials() and resending the request again with WinHttpSendRequest(). Since I really need chunked transfer, is it possible to use it in combination with windows authentication?
Here is the log of audit failure when using chunked encoding:
SubjectUserSid S-1-5-18
SubjectUserName MY-PC-NAME$
SubjectDomainName WORKGROUP
SubjectLogonId 0x3e7
TargetUserSid S-1-0-0
TargetUserName Administrator
TargetDomainName MY-PC-NAME
Status 0xc000006d
FailureReason %%2313
SubStatus 0xc000006a
LogonType 2
LogonProcessName User32
AuthenticationPackageName Negotiate
WorkstationName MY-PC-NAME
TransmittedServices -
LmPackageName -
KeyLength 0
ProcessId 0x1d8
ProcessName C:\Windows\System32\winlogon.exe
IpAddress 127.0.0.1
IpPort 0
And, here is the successful audit without chunked encoding:
SubjectUserSid S-1-0-0
SubjectUserName -
SubjectDomainName -
SubjectLogonId 0x0
TargetUserSid S-1-5-21-4112068699-3954607238-3758397191-1005
TargetUserName moose
TargetDomainName MY-PC-NAME
TargetLogonId 0x137576fb8
LogonType 3
LogonProcessName NtLmSsp
AuthenticationPackageName NTLM
WorkstationName M_10
LogonGuid {00000000-0000-0000-0000-000000000000}
TransmittedServices -
LmPackageName NTLM V2
KeyLength 128
ProcessId 0x0
ProcessName -
IpAddress 167.109.28.37
IpPort 56695
As you can see, so many things are different: domain, username, IP, port, Logon type, TargetUserSid, etc...
And all beacuse of "Transfer-Encoding: Chunked\r\n"!?
EDIT:
To make it simple, the question is:
success = WinHttpSetCredentials( hPostRequest, target, authScheme, IIS_USER_NAME, IIS_PWD, NULL );
success = WinHttpSendRequest( hPostRequest,
L"Transfer-Encoding: Chunked\r\n",
(DWORD)-1,
WINHTTP_NO_REQUEST_DATA,
0,
WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH,
0 );
After successful return values, what WinHttp API to call next?

Related

Fetch emails through IMAP with proxy of form user:password:host:port

I have code to login to my email account to fetch recent emails:
def fetchRecentEmail(emailAddr, emailPassword, timeout=120):
host = fetch_imap_server(emailAddr) # e.g. 'outlook.office365.com'
with IMAP4_SSL(host) as session:
status, _ = session.login(emailAddr, emailPassword)
if status == 'OK':
# fetch most recent message
status, messageData = session.select("Inbox")
:
I'm trying to tweak it to go through a proxy.
ref: How can I fetch emails via POP or IMAP through a proxy?
ref: https://gist.github.com/sstevan/efccf3d5d3e73039c21aa848353ff52f
In each of the above resources, the proxy is of clean form IP:PORT.
However my proxy is of the form USER:PASS:HOST:PORT.
The proxy works:
USER = 'Pp7fwti5n-res-any-sid-' + random8Digits()
PASS = 'abEDxts7v'
HOST = 'gw.proxy.rainproxy.io'
PORT = 5959
proxy = f'{USER}:{PASS}#{HOST}:{PORT}'
proxies = {
'http': 'http://' + proxy,
'https': 'http://' + proxy
}
response = requests.get(
'https://ip.nf/me.json',
proxies=proxies, timeout=15
)
The following code looks like it should work, but errors:
HOST = 'outlook.office365.com'
IMAP_PORT = 963
PROXY_TYPE = 'http' # rainproxies are HTTP
mailbox = SocksIMAP4SSL(
host=HOST,
port=IMAP_PORT,
proxy_type=PROXY_TYPE,
proxy_addr=URL,
proxy_port=PORT,
username=USER,
password=PASS
)
emailAddress, emailPassword = EMAIL.split(',')
mailbox.login(emailAddress, emailPassword)
typ, data = mailbox.list()
print(typ)
print(data)
I needed to add a timeout arg/param in 2 places to get the code to run:
def _create_socket(self, timeout=None):
sock = SocksIMAP4._create_socket(self, timeout)
server_hostname = self.host if ssl.HAS_SNI else None
return self.ssl_context.wrap_socket(
sock, server_hostname=server_hostname
)
def open(self, host='', port=IMAP4_PORT, timeout=None):
SocksIMAP4.open(self, host, port, timeout)
Rather confusing that nobody else seems to have flagged that in the gist.
But it still won't work.
If I use any number other than 443 for IMAP_PORT I get this error:
GeneralProxyError: Socket error: 403: Forbidden
[*] Note: The HTTP proxy server may not be supported by PySocks (must be a CONNECT tunnel proxy)
And if I use 443, while I now get no error, mailbox = SocksIMAP4SSL( never completes.
So I am still far from a working solution.
I am hoping to run this code simultaneously on 2 CPU cores, so I don't understand the implications of using port 443. Is that going to mean that no other process on my system can use that port? And if this code is using this port simultaneously in two processes, does this mean that there will be a conflict?
Maybe you can try monkeypatching socket.socket with PySocket.
import socket
import socks
socks.set_default_proxy(socks.SOCKS5, HOST, PORT, True, USER, PASS)
socket.socket = socks.socksocket
Then check if your IMAP traffic is going through a given proxy.

Certificate Auth. : the specified credentials were rejected by the server

It's been multiple days since I started trying enabling all my Windows hosts to be reachable with Ansible via the certificate authentication method. I use a script to configure WinRM and to create a self-signed certificate. On multiple hosts, it works fine and after the script is finished I can connect to them via certificate authentication but on some other (like 15-20% of them) it's impossible.
I get this error message:
fatal: [SERVERNAME]: UNREACHABLE! => {
"changed": false,
"msg": "certificate: the specified credentials were rejected by the server",
"unreachable": true
}
What is strange is that I don't see the login event in the Windows event viewer. Here is my WinRM configuration:
Service
RootSDDL = O:NSG:BAD:P(A;;GA;;;BA)(A;;GR;;;IU)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD)
MaxConcurrentOperations = 4294967295
MaxConcurrentOperationsPerUser = 1500
EnumerationTimeoutms = 240000
MaxConnections = 300
MaxPacketRetrievalTimeSeconds = 120
AllowUnencrypted = false
Auth
Basic = true
Kerberos = true
Negotiate = true
Certificate = true
CredSSP = true
CbtHardeningLevel = Relaxed
DefaultPorts
HTTP = 5985
HTTPS = 5986
IPv4Filter = *
IPv6Filter = *
EnableCompatibilityHttpListener = false
EnableCompatibilityHttpsListener = false
CertificateThumbprint
AllowRemoteAccess = true
Winrs
AllowRemoteShellAccess = true
IdleTimeout = 7200000
MaxConcurrentUsers = 10
MaxShellRunTime = 2147483647
MaxProcessesPerShell = 25
MaxMemoryPerShellMB = 1024
MaxShellsPerUser = 30
Both the listener and the certificate mapping are configured on the windows machine:
Listener
Address = *
Transport = HTTPS
Port = 5986
Hostname
Enabled = true
URLPrefix = wsman
CertificateThumbprint = 927...C26E
ListeningOn = 127.0.0.1, 172.20.x.x
CertMapping
URI = *
Subject = ansibleuser#localhost
Issuer = 579f3eb1c3756339a246843f70e1a89b14fdc244
UserName = ansibleuser
Enabled = true
Password
What I've tried up until now:
Check the presence of the LocalAccountTokenFilterPolicy registry key
Configure the access to WinRM through winrm configSDDL default
Check GPOs
Change the password (check and uncheck password never expires,
etc...)
Create another local admin user
Enable basic and unencrypted authentication
Change the connection type to private (could not since the servers
are domain joined)
Run the script provided by ansible to configure WinRM
I don't understand what is going on and it's driving me nuts. Did someone encounter this problem before ?
I'm open to all suggestions, thanks in advance.
FINALLY found a solution to this problem:
Based on this thread Client Certificate-based authentication stopped working for PS Remoting, I found out that a registry key named "ClientAuthTrustMode" should be set to the value "2" and, with that, the error message magically disappears.
Here is a Microsoft article detailing the implication of the key : Overview of TLS - SSL (Schannel SSP)
Here is a simple powershell command to flip the switch :
Set-ItemProperty -Path registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL -Name ClientAuthTrustMode -Type DWord -Value 2
Hopefully that will help someone out there.
Thanks for Your solution! It solves my problem also.
We had lot of servers using ansible and WINRM with certificate based authentication, but only one of our servers had the same issue as yours was...
The only one interesting difference, what I've found on your shared settings is this:
ListeningOn = 127.0.0.1, 172.20.x.x
This is also same as mine... localhost is in the first place...
ListeningOn = 127.0.0.1, 19x.1xx.19.98, ::1
Other servers in our setup is add the network interface on the first place like this:
ListeningOn = 19x.16.61.38, 127.0.0.1, ::1
I really don't know, it matters or not, but this is the only difference, what I've found.
Thanks a lot.

Outlook SMTP Oauth Send - Authentication Unsuccessful

I have been trying to implement email functionality in a legacy app using the Legacy Mail API to add OAuth support after getting a token through the Device Code Flow starting with the Microsoft Sample Project.
Along that route I have added SMTP.Send and many other API permissions to find the missing piece. (Including { "User.Read", "User.ReadBasic.All", "SMTP.Send", "offline_access", "Mail.Send" } in fear of missing one)
I have been testing with the MailKit library to build a proof of concept.
So far I have the following Code Snippet that fails after trying to authenticate.
public void SendSmtpMessageAsync(string id, string accessToken)
{
var message = new MimeMessage();
message.From.Add(new MailboxAddress("From Name", "From Address#Example.com"));
message.To.Add(new MailboxAddress("To Name", "To Address#Example.com"));
message.Subject = "How you doin'?";
message.Body = new TextPart("plain")
{
Text = #"Test Email Content"
};
using (var client = new SmtpClient(new ProtocolLogger(Console.OpenStandardOutput())))
{
try
{
client.Connect("smtp.office365.com", 587, SecureSocketOptions.StartTls);
var oauth2 = new SaslMechanismOAuth2(id, accessToken);
var temp = client.AuthenticationMechanisms;
client.Authenticate(oauth2);
client.Send(message);
client.Disconnect(true);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
MailKit Log
I have enabled logging and gotten a log that shows the client connecting than sending the token to authenticate but the authentication failing.
Connected to smtp://smtp.office365.com:587/?starttls=always
S: 220 MW3PR05CA0003.outlook.office365.com Microsoft ESMTP MAIL Service ready at Mon, 25 May 2020 21:31:07 +0000
C: EHLO [192.168.0.7]
S: 250-MW3PR05CA0003.outlook.office365.com Hello [<<My IP>>]
S: 250-SIZE 157286400
S: 250-PIPELINING
S: 250-DSN
S: 250-ENHANCEDSTATUSCODES
S: 250-STARTTLS
S: 250-8BITMIME
S: 250-BINARYMIME
S: 250-CHUNKING
S: 250 SMTPUTF8
C: STARTTLS
S: 220 2.0.0 SMTP server ready
C: EHLO [192.168.0.7]
S: 250-MW3PR05CA0003.outlook.office365.com Hello [<<My IP>>]
S: 250-SIZE 157286400
S: 250-PIPELINING
S: 250-DSN
S: 250-ENHANCEDSTATUSCODES
S: 250-AUTH LOGIN XOAUTH2
S: 250-8BITMIME
S: 250-BINARYMIME
S: 250-CHUNKING
S: 250 SMTPUTF8
C: AUTH XOAUTH2 <<Token omitted but I have confirmed that it is Base64 encoded and
in the format of base64("user=" + userName + "^Aauth=Bearer " + accessToken + "^A^A")>>
S: 535 5.7.3 Authentication unsuccessful [MW3PR05CA0003.namprd05.prod.outlook.com]
MailKit.Security.AuthenticationException: 535: 5.7.3 Authentication unsuccessful
[MW3PR05CA0003.namprd05.prod.outlook.com]
Any direction or resources would be appreciated since most existing posts are from pre-2020 when Legacy SMTP support was added. Additionally, if you see any misunderstanding let me know so I can do some additional reading.
After lots of searching and trying to talk to Microsoft I was pointed in the direction of this answer of a different post. (I had adding a POP3 call that was in the same format as the SMTP call in the OP.) The answer said to include the scope https://outlook.office.com/POP.AccessAsUser.All so after replacing the POP.AccessAsUser.All I had before the SMTP and POP3 calls worked using modern authentication.
Additionally, now that I know the answer I see it is documented in Microsoft's Docs Authenticate an IMAP, POP or SMTP connection using OAuth verifying that this is the proper solution.
Make sure to specify the full scopes, including Outlook resource URLs, when authorizing your application and requesting an access token.
| Protocol | Permission scope string
|-----------|-------------------------------------
| IMAP | https://outlook.office.com/IMAP.AccessAsUser.All
| POP | https://outlook.office.com/POP.AccessAsUser.All
| SMTP AUTH | https://outlook.office.com/SMTP.Send
Hope this helps anyone else facing this same issue
After spending hours and days trying to solve the Authentication unsuccessful error here's an important thing I discovered:
When requesting "scopes" during oAuth2 flow make sure you don't request any scopes, other than offline_access and https://outlook.office.com/SMTP.Send
In my case the problem was - I was also requesting the User.Read scope
I had a similar problem, which was cause by my misconfiguration of Microsoft 365. Please see MailKit unsuccessful SMTP OAuth with Microsoft 365 server for details. Hope this helps.

WinRM connectivity issue?

I am trying to have a Enter-PSSession with a Company server with in the company network. I can RDC to the server, ping the server and also get the Windows Services status using Get-Service -ComputerName DBServer. However, WinRM session does not allow me to get into the server.
My PC:
Windows 10
Powershell 5.0
IP: 128.2.60.102
Server:
Windows Server 2012
PowerShell 4.0
IP: 10.1.130.1
On DBServer:
PS C:\Windows\system32> winrm e winrm/config/listerner
WSManFault
Message
ProviderFault
WSManFault
Message = The WS-Management service cannot process the
request. The resource URI does not support the
Enumerate operation.
Error number: -2144108495 0x80338031
The WS-Management service cannot process the request because the WS-
Addressing Action URI in the request is not compatible with the resource.
PS C:\Windows\system32> winrm quickconfig
WinRM service is already running on this machine.
WinRM is already set up for remote management on this computer.
PS C:\Windows\system32> winrm get winrm/config
Config
MaxEnvelopeSizekb = 500
MaxTimeoutms = 60000
MaxBatchItems = 32000
MaxProviderRequests = 4294967295
Client
NetworkDelayms = 5000
URLPrefix = wsman
AllowUnencrypted = false
Auth
Basic = true
Digest = true
Kerberos = true
Negotiate = true
Certificate = true
CredSSP = false
DefaultPorts
HTTP = 5985
HTTPS = 5986
TrustedHosts
Service
RootSDDL = O:NSG:BAD:P(A;;GA;;;BA)(A;;GR;;;IU)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD)
MaxConcurrentOperations = 4294967295
MaxConcurrentOperationsPerUser = 1500
EnumerationTimeoutms = 240000
MaxConnections = 300
MaxPacketRetrievalTimeSeconds = 120
AllowUnencrypted = false
Auth
Basic = false
Kerberos = true
Negotiate = true
Certificate = false
CredSSP = false
CbtHardeningLevel = Relaxed
DefaultPorts
HTTP = 5985
HTTPS = 5986
IPv4Filter = *
IPv6Filter = *
EnableCompatibilityHttpListener = false
EnableCompatibilityHttpsListener = false
CertificateThumbprint
AllowRemoteAccess = true
Winrs
AllowRemoteShellAccess = true
IdleTimeout = 7200000
MaxConcurrentUsers = 10
MaxShellRunTime = 2147483647
MaxProcessesPerShell = 25
MaxMemoryPerShellMB = 1024
MaxShellsPerUser = 30
On Client(My machine):
PS C:\windows\system32> Test-WSMan -ComputerName "DBServer"
Test-WSMan : <f:WSManFault xmlns:f="http://schemas.microsoft.com/wbem/wsman/
1/wsmanfault" Code="2150859046" Machine="MyMachine"><f:Message>WinRM cannot
complete the operation. Verify that the specified computer name is valid, that
the computer is accessible over the network, and that a firewall exception for
the WinRM service is enabled and allows access from this computer. By default,
the WinRM firewall exception for public profiles limits access to remote
computers within the same local subnet. </f:Message></f:WSManFault>
At line:1 char:1
+ Test-WSMan -ComputerName "DBServer"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (DBServer:String) [Test-WSMan], InvalidOperationException
+ FullyQualifiedErrorId : WsManError,Microsoft.WSMan.Management.TestWSManCommand
PS C:\windows\system32> winrm e winrm/config/listener
Listener
Address = *
Transport = HTTP
Port = 5985
Hostname
Enabled = true
URLPrefix = wsman
CertificateThumbprint
ListeningOn = 127.0.0.1, 128.1.60.202, ::1
PS C:\windows\system32> winrm quickconfig
WinRM service is already running on this machine.
WinRM is already set up for remote management on this computer.
Firewall ports for WinRM are open for both HTTP and HTTPS.
Can anyone help with this issue?
Note: The following is likely not the root cause of your problem, but it explains the error message you saw on the server, which is cryptic and deserves an explanation (a simple typo confounded me too and led me here).
You have a typo in the resource URI you're passing to winrm e on your server:
winrm e winrm/config/listerner # note the extra "r"
should be:
winrm e winrm/config/listener
Unfortunately, **a reference to a nonexistent resource URI results in the following cryptic error message:
WSManFault
Message
ProviderFault
WSManFault
Message = The WS-Management service cannot process the request. The resource URI does not support the Enumerate operation.
Error number: -2144108495 0x80338031
The WS-Management service cannot process the request because the WS-Addressing Action URI in the request is not compatible with the resource.

Win32: How to validate credentials against Active Directory?

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;
}
}

Resources