I am updating a legacy Delphi 5 application (that we have code for) that authenticates against LDAP/AD by binding using user-provided credentials. However, the libraries we're using cannot work to bind against LDAPS (LDAP/SSL) over ports 636 or 3269.
I see that authenticating against LDAP/AD in C# can be as simple as:
PrincipalContext pc = new PrincipalContext(ContextType.Domain, "testnet.testad.org:636", "dc=testnet,dc=testad,dc=org");
bool validated = pc.ValidateCredentials(username, password, ContextOptions.Negotiate);
I am thinking of wrapping the necessary calls in a .DLL, then invoking the call from within the Delphi app. However, I'm presuming that there is a minefield of 'gotchas' that would prevent it from being this easy.
Using UnmanagedExports this is really quite easy.
You start with an interface for the functionality, like so:
type
IManagedInterface = interface
['{9F5A2431-5559-410C-BAB4-5144CA8C0B7B}']
function CheckCredentials(AContextName, AContainerName, AUserName,
APassword: WideString): integer; safecall;
end;
This is probably not the best choice of parameter types, as you can't call that with NULL
for user and password, and you would probably want to pass the other parameters (like context type) as well, but you get the idea.
Then you create a new Class Library (.NET Framework) project in VS 2019, setting the build target to x86 (for it to be compatible with Delphi 5). You add the NuGet package for UnmanagedExports.
Within that project you add the same interface:
[ComVisible(true)]
[Guid("9F5A2431-5559-410C-BAB4-5144CA8C0B7B")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IManagedInterface
{
int CheckCredentials(
[MarshalAs(UnmanagedType.BStr)] string contextname,
[MarshalAs(UnmanagedType.BStr)] string container,
[MarshalAs(UnmanagedType.BStr)] string username,
[MarshalAs(UnmanagedType.BStr)] string password);
}
and a class which implements that interface:
public class Class1 : IManagedInterface
{
public int CheckCredentials(string contextname, string container,
string username, string password)
{
var pc = new PrincipalContext(ContextType.Domain, contextname, container);
return Convert.ToInt32(pc.ValidateCredentials(username, password,
ContextOptions.Negotiate));
}
}
Finally you export a function from the DLL that creates and returns an instance of this class:
static class Exports
{
[DllExport]
public static void CreateTheInterface(
[MarshalAs(UnmanagedType.Interface)] out IManagedInterface instance)
{
instance = new Class1();
}
}
Now you can use that DLL from Delphi (to keep this short the library is not loaded on demand, which I would do in production code):
procedure CreateTheInterface(out AInstance: IManagedInterface); stdcall;
external 'ClassLibrary1.dll';
var
Imi: IManagedInterface;
context, container, username, password: string;
begin
CreateTheInterface(Imi);
if Imi <> nil then try
context := 'testnet.testad.org:636';
container := 'dc=testnet,dc=testad,dc=org';
username := 'someguy';
password := 'password';
Writeln('call returned: ',
Imi.CheckCredentials(context, container, username, password));
except
on E: Exception do
Writeln(E.Message);
end;
Readln;
end.
So, no minefield of 'gotchas' and it is really quite easy.