Background:
This is my first time trying to acces an LDAP server via C# VS.NET code.
The LDAP server is actually an LDAP service running on a IBM Z/OS Mainframe.
We have verified it using bach OMVS commands. However, when trying to code
to access this, the above exception is encountered. The code is simple:
DirectoryEntry de = new DirectoryEntry("ldap://serverDNS:9503", "racfid=" +
"V121119", "sept04", AuthenticationTypes.Encryption);
Object o = de.NativeObject; //this will force the bind
Ofcourse, serverDNS is substituted for the real DNS value.
The exception is generated on this last statement.
The LDAP was set up on port 9503 and that is why I am using :9503. I am
using 'ldap' in lower case b/c when I try upper case, I receive an error that
the
server is not operational. Since this LDAP is actually being used to
validate racf id's & pwd's on the mainframe, I am using racfid=....
This piece of code running on the MF server itself was used to validate the
same ID and it worked fine:
OSHELL ldapsearch -h serverDNS -p 9503 +
-D racfid=v121119,profiletype=user,sysplex=badqplex +
-w sept04 +
-b "racfid=v121119,profiletype=user,sysplex=badqplex" +
"objectclass=*" ;
Ofcourse serverDNS is again substituted from the real DNS.
Can some one help me figure out the correct values to put in the
DirectoryEntry constructor?
Thanks,
Tim
Also, make sure that your user id syntax is correct for the directory you
are targeting.
Joe K.
"Tim Reynolds" <TimRe...@discussions.microsoft.com> wrote in message
news:1D835652-D7C6-493F...@microsoft.com...
Thanks.
Jason Tyler
Microsoft AD/ADSI Developer Support
Thanks again,
Tim Reynolds
The first thing I'd try to do is to use the ldp.exe tool that comes with the
Windows Support Tools or ADAM installation to try to connect. This allows
you to try different binding options and is a more pure LDAP API client.
Once you get that working, then you can generally work backwards to figure
out what to use in code to bind.
One thing right off the top is that you need upper case LDAP in your binding
string. Lower case doesn't work for some reason. Sorry I didn't notice
that before.
Joe K.
"Tim Reynolds" <TimRe...@discussions.microsoft.com> wrote in message
news:5F302EAE-2120-4792...@microsoft.com...
DirectoryEntry objDirEnt = new
DirectoryEntry("LDAP://BADEVL.verizon.com:9503/profiletype=user,CN=ldapadmin,O=vzds", "racfid=V121119", "sept04", AuthenticationTypes.Anonymous);
Object o = objDirEnt.NativeObject; //this will force the bind
However, I want to simply use LDAP to verify if a given userid/pw is valid.
Where do I go from here? Is this correct code - how do I use it.
Newbie,
Tim Reynolds
Thanks....
Generally with S.DS, you want to bind to an object that the user will be
allowed to view if the supplied credentials are valid. There may be one
object globally you can use for this (like RootDSE in Active Directory or
other LDAP V3 databases) or the domain root. You may also need to bind to
the user's own object in the directory. This all depends on authorization
policy.
In any event, that will affect the DN part of the binding string (after the
9503/ in your sample).
You would definitely want to use AuthenticationTypes.None as
AuthenticationTypes.Anonymous won't do any authentication. If you need to
protect the credentials on the wire, then you must use SecureSocketsLayer,
but that requires that the LDAP server be configured for SSL/LDAP and have a
valid certificate.
Normally, to see if the bind works, you put the Object o =
objDirEnt.NativeObject line in a try/catch block and catch the COMException
that will be thrown if the bind fails. If you want to be really picky, you
can look for the specific HRESULT (in the ErrorCode property) that indicates
bad credentials (0x8007052eL).
That should give you enough for a boolean test that you can use to do
authentication against the other directory.
HTH,
Joe K.
"Tim Reynolds" <TimRe...@discussions.microsoft.com> wrote in message
news:85D19FC7-4157-4C41...@microsoft.com...
I have tried using AuthenticationTypes.None, but I receive
System.Runtime.InteropServices.COMException (0x8007052E): Logon failure:
unknown
user name or bad password
each time...
So I am guessing that my DN is still bad.
If I just use the constructor that only takes a path - I can bind with no
problem, but I this obviously isn't proving much:
DirectoryEntry objDirEnt = new
DirectoryEntry("LDAP://BADEVL.verizon.com:9503/profiletype=user,CN=ldapadmin,O=vzds");
It does confirm that the DN is there, but as for sending a valid user & pw
and having it work, I haven't been able to complete that yet. In fact, all
the AuthenticationTypes except Anonomous fail for one reason of the other.
As for the ldp.exe tool, I have been able to establish the connection, but I
have not been able to get a successful bind. I am not sure how to pass the
userid/pw I want in the bind (I'm guessing it is using my windows id&pw).
Here is what I get from that tool:
res = ldap_bind_s(ld, NULL, &NtAuthIdentity, 1158); // v.3
{NtAuthIdentity: User='v121119'; Pwd= <unavailable>; domain =
'cn=ldapadmin,o=vzds'.}
Error <86>: ldap_bind_s() failed: Auth Unknown.
res = ldap_bind_s(ld, NULL, &NtAuthIdentity, 1158); // v.3
{NtAuthIdentity: User='v121119'; Pwd= <unavailable>; domain =
'profiletype=user,cn=ldapadmin,o=vzds'.}
Error <86>: ldap_bind_s() failed: Auth Unknown.
0x0 = ldap_unbind(ld);
I'm on the trail - so if you have any other advice, I'll gladly take.
Thanks,
AuthenticationTypes.Secure, Sealing, Signing and Delegation are Windows
authentication features and probably should not be used with any directory
that isn't AD. I'd be very surprised to find a mainframe LDAP directory
that supports Windows negotiate authentication using GSSAPI.
AuthenticationTypes.None basically means a simple bind in LDAP terms. All
LDAP servers should support this. You can combine this flag with
SecureSocketsLayer if you want to use SSL/TLS channel encryption and your
server is configured with a proper certificate. Otherwise, credentials are
passed in plain text on the network.
AuthenticationTypes.Anonymous (as far as I know) doesn't perform an LDAP
bind, but searches anonymously. This is not what you want here as the bind
is what is doing the authentication.
ServerBind is just a performance optimization that you can use if you supply
an explicit server. The client stack will skip a DNS lookup for you if you
specify this flag. You could probably use that here since you are
specifying a specific host.
My guess for you is that either your username syntax is wrong (you might
need the full distinguished name for example) or the object you are binding
to doesn't allow you to view it. I'd suspect the former first though.
Regarding ldp.exe, make sure you uncheck the "domain" check box so that
Windows authentication isn't used. You want to use simple bind if possible.
I hope that sheds some more light.
Joe K.
"Tim Reynolds" <TimRe...@discussions.microsoft.com> wrote in message
news:A5C0D7FA-D238-4EC2...@microsoft.com...
We finally have some connectivity with our LDAP on the Main Frame.
However, when the id or pw is wrong or when pw has expired, i get the same
generic exception:
Runtime.InteropServices.ComException with message 'unknown user name or bad
password'
I should be able to get the real return code out of LDAP.
I can see in the LDAP trace on the mainframe the real reason - like (R000100
The pass word has expired.)
However, I am wondering how to get that back in my C# module.
My C# code is simply:
DirectoryEntry objDirEnt = new
DirectoryEntry("LDAP://BADEVL.verizon.com:9503/profiletype=user,cn=ldapadmin,o=vzds",
"racfid=V230093,profiletype=user,cn=ldapadmin,o=vzds", "pwgoeshere",
AuthenticationTypes.Delegation);
Object o = objDirEnt.NativeObject; //this will force the bind
This code is in a try / catch block. If no exception - it is valid id/pw -
this works - finally. However, for any other reason - bad id/pw/or expired
pw - it throws a generic exception noted above.
Any clues on how to get the real LDAP return code?
Thanks,
Tim
Lee Flight
"Tim Reynolds" <TimRe...@discussions.microsoft.com> wrote in message
news:7298AE05-95F4-4943...@microsoft.com...
Public Shared Function GetErrorMessageForLastAdsiError(ByVal hresult
As Integer) As String
Dim win32Facility As Integer = &H80070000
Dim bufferSize As Int32 = 256
Dim hr As Int32
Dim functionError As Int32
Dim errorDescription As StringBuilder
Dim nameDescription As StringBuilder
Dim errorCode As Int32
If (hresult And win32Facility) = win32Facility Then
errorDescription = New StringBuilder(bufferSize)
nameDescription = New StringBuilder(bufferSize)
hr = ADsGetLastError(errorCode, errorDescription,
bufferSize, nameDescription, bufferSize)
If hr = 0 Then
Return String.Format("{0}: {1} ({2})", errorCode,
errorDescription, nameDescription)
Else
functionError = Marshal.GetLastWin32Error()
Throw New
System.ComponentModel.Win32Exception(functionError)
End If
Else
Return String.Empty
End If
End Function
<DllImport("activeds.dll", SetLastError:=True,
CharSet:=CharSet.Unicode)> _
Private Shared Function ADsGetLastError(ByRef lpError As Int32,
ByVal lpErrorBuf As StringBuilder, ByVal dwErrorBufLen As Int32, ByVal
lpNameBuf As StringBuilder, ByVal dwNameBufLen As Int32) As Int32
End Function
If that works, please let us know as lots of people would be interested.
However, in the interests of security, you may not want to reveal why an
authentication attempt failed as that might be giving more ammo to an
attacker. Just something to think about. That's why the actual error is so
obscure in the first place.
Joe K.
"Lee Flight" <l...@le.ac.uk-nospam> wrote in message
news:u2fiYY6t...@TK2MSFTNGP12.phx.gbl...
try {
ctx = new InitialContext(env);
status = VALID; // the bind worked - user is authenticated
retval = true;
}
catch (NamingException e){
analyzeLdapException(e);
}
They actually have code to look for LDAP: error code 49 - R and then get the
error number.
So I assume, if I want this I can't used managed code - I have to use
something like Java?
With just a generic interface that can't tell me when a password is expired,
there obviously would be no way to tell the user they need to put in a new
password... How would that work? Seems like a big limitation of using this
product.
Thanks,
Tim
FWIW I tried a P/Invoke of ADsGetLastError some time back and got
nothing out of it :( I was prepared to believe that this was poor coding
on my part as it was at the limit of my skill set, however subsequent
investigation convinced me that the LDAP library was blocking this
information. I raised this informally with Microsoft and apparently
this is by design and may now be under review.
My argument is that there is no security in this either inherent or by
obscurity as other (non-MS) windows LDAP libraries and LDAP
libraries running on other OS will give the information as will a
network sniff.
Lee Flight
"Joe Kaplan (MVP - ADSI)" <joseph....@removethis.accenture.com> wrote
in message news:%23BMV5e6...@TK2MSFTNGP10.phx.gbl...
You could try opening an incident with PSS to get the definitive
answer and hope to get a Design Change Request through if the
problem is confirmed.
Lee Flight
"Tim Reynolds" <TimRe...@discussions.microsoft.com> wrote in message
news:4D4601E5-4B6B-48AA...@microsoft.com...
The code I am using is a slight modification of yours and is pasted below to
see if you see any errors with it. In any case, it is not pulling up a more
clear description.
As for your comment about security hiding some info - I agree overall - but
in the case of a need to change pw, the user needs to know this. As for a
wrong id or pw - it should be generic - agreed.
Thanks,Tim
catch(COMException e)
{
string exceptionError = GetErrorMessageForLastAdsiError(e.ErrorCode);
Console.WriteLine(exceptionError);
Console.WriteLine(e.ToString());
Console.ReadLine();
}
public static string GetErrorMessageForLastAdsiError(int hresult)
{
int bufferSize = 256;
int functionError;
StringBuilder errorDescription;
StringBuilder nameDescription;
uint errorCode;
errorDescription = new StringBuilder(bufferSize);
nameDescription = new StringBuilder(bufferSize);
ADsGetLastError(out errorCode, errorDescription, bufferSize,
nameDescription, bufferSize);
if (errorCode == 0)
{
return String.Format("{0}: {1} ({2})", errorCode,errorDescription,
nameDescription);
}
else
{
functionError = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(functionError);
}
}
[DllImport("activeds.dll",CharSet=CharSet.Unicode,PreserveSig=false)]
static extern void ADsGetLastError(out uint lpError, StringBuilder
lpErrorBuf, int dwErrorBufLen, StringBuilder lpNameBuf, int
dwNameBufLen);
.......
I think you were calling the function correctly, passing the ErrorCode
property of the COMException in. It does work for other stuff.
I agree that having the failure reason, especially if the directory provides
it, can be very helpful. People complain about this all the time with LDAP
authentication and AD.
If you want a .NET solution for a straight LDAP API wrapper, I can think of
three other possibilities for you:
- System.DirectoryServices.Protocols in .NET 2.0 does this (but you have to
use the beta framework to get this now...)
- The Novell C# LDAP library would probably do this fine
- I have a custom p/invoke library created by an MS guy that I use for some
stuff that would probably work for this. I can send it to you offline if
you want it.
Cheers,
Joe K.
"Tim Reynolds" <TimRe...@discussions.microsoft.com> wrote in message
news:2CD71266-D37B-46A4...@microsoft.com...
Please send....
Lee Flight
"Joe Kaplan (MVP - ADSI)" <joseph....@removethis.accenture.com> wrote
in message news:OXJ3RT7t...@TK2MSFTNGP10.phx.gbl...
The other cool thing is that it can be used to get data via native LDAP
calls OR through DSML using, XML, SOAP and HTTP if you have a DSML server
configured. The API is the same, you just initialize it differently.
I have had very little exposure to it so far, but it looks like it will be
helpful to people who need it. I don't know enough to answer any questions
on it yet though.
Joe K.
"Lee Flight" <l...@le.ac.uk-nospam> wrote in message
news:epbh6c7t...@TK2MSFTNGP12.phx.gbl...
Joe K.
"Tim Reynolds" <TimRe...@discussions.microsoft.com> wrote in message
news:6CC5E3FF-9B1B-4D11...@microsoft.com...
Send me a note here: joe @ joekaplan . net
(remove spaces obviously).
Cheers,
Joe K.
"Tim Reynolds" <TimRe...@discussions.microsoft.com> wrote in message
news:00B53DF8-1D4F-4C41...@microsoft.com...
Also wondering where I can get info about the Novell C# LDAP library....
Thanks,Tim
You will need to register (free) to download.
Lee Flight
"Tim Reynolds" <TimRe...@discussions.microsoft.com> wrote in message
news:1DA9CCA1-BFCC-4329...@microsoft.com...