7

We have a C# project, which interacts with Active Directory services.

For the context : we use objects from the System.DirectoryServices.Protocols namespace, namely :

  • LdapConnection to connect to the server
  • SearchRequest to scan through entries
  • DirSyncRequestControl to use DirSync capabilities on the SearchRequest

We got stuck for some time on understanding an error which triggered a DirectoryOperationException, before realizing the description of the error what not included in the exception.Message, but was nested further down in the exception object.

We used to have a very simple exception logging when catching such an error :

catch (DirectoryOperationError de) {
    log("ERROR directory error {0} : {1}", de.GetType(), de.Message);
    throw;
}

We now have the following code :

catch (DirectoryOperationException de)
{
    log("ERROR directory error {0} : {1}", de.GetType(), de.Message);

    var resp = de.Response;
    if (resp == null)
    {
        log("          -- no response object linked to exception --");
        throw;
    }

    log("ERROR     directoryresponse error message:'{0}'", resp.ErrorMessage);

    int errorCode;
    var hexCode = resp.ErrorMessage.Substring(0, 8);
    if (!int.TryParse(hexCode, System.Globalization.NumberStyles.HexNumber, null, out errorCode)){
        log("          -- could not figure out error code from '{0}' --", hexCode);
        throw;
    }

    var win32exception = new System.ComponentModel.Win32Exception(errorCode);
    var msg = win32exception.Message;

    log("ERROR     errcode:{0} : {1}", errorCode, msg);

    throw;
}

which ranks pretty high on my "hocus pocus" scale (especially the part where we rely on the string message beginning by an 8 char long hex integer).

Question

Is there a more direct way to access the underlying LDAPError and translate it into a meaningful message using C# ?

LeGEC
  • 46,477
  • 5
  • 57
  • 104
  • Are you using `LdapConnection` to talk to AD or `DirectoryEntry`? Do you get the same error code in `de.HResult`? – Gabriel Luci Sep 04 '19 at 14:59
  • We are using `LdapConnection`. We did not check the value of `de.HResult`, we'll look into that. – LeGEC Sep 04 '19 at 15:01
  • 1
    `LdapConnection` is really created for generic LDAP communication (AD or not), so it has no real optimizations for AD. `DirectoryEntry` works more closely with AD and will translate a lot of the error messages for you to something meaningful. I'm not suggesting you change that - that might be a lot of work. – Gabriel Luci Sep 04 '19 at 15:07
  • @GabrielLuci : noted. I didn't post the code above : the exe we are debugging runs a SearchRequest with DirSync options (and reloads Cookies from previous iterations). By quickly scanning in System.DirectoryService, I didn't see this capabilty on Directory* objects. I will look further into it. – LeGEC Sep 04 '19 at 15:39
  • I think you're looking for the [`DirectorySynchronization`](https://learn.microsoft.com/en-us/dotnet/api/system.directoryservices.directorysearcher.directorysynchronization) property on [`DirectorySearcher`](https://learn.microsoft.com/en-us/dotnet/api/system.directoryservices.directorysearcher). There's an example of how to use it in the docs for the [`DirectorySynchronization`](https://learn.microsoft.com/en-us/dotnet/api/system.directoryservices.directorysynchronization) class. – Gabriel Luci Sep 04 '19 at 16:20
  • I have the same issue. How can I translate the hex integer to an actual error message? Is there a dictionary online that maps them? – Victorio Berra Dec 09 '22 at 03:22
  • @VictorioBerra unfortunately if you look at my way of detect File Is Locked its *voodoo magic* https://stackoverflow.com/a/11060322/495455 – Jeremy Thompson Dec 09 '22 at 04:34
  • @VictorioBerra: well technically, I pasted in my question one way to turn the hex integer into a message : decode `errorCode` from the hex value, get `Win32Exception(errorCode).Message`. Are you looking to translate these codes from a non windows system ? – LeGEC Dec 09 '22 at 06:33
  • @LeGEC I am using a Windows Azure App Service, will this work on that system? – Victorio Berra Dec 10 '22 at 15:55
  • @LeGEC I missed that originally, `Win32Exception(errorCode).Message`. For the record, it does also work on App Services (Windows). But you are right, it feels like magic. Internally the code calls `Marshal.GetLastPInvokeError()` and I don't know what that does. What if there were two errors simultaneously? Would that be reliable? Should we just check the errors now, and then write code to handle the hex codes instead? IE: `if ErrorMessage.Contains("00000052") then "Invalid password."` – Victorio Berra Dec 10 '22 at 22:32
  • 1
    It doesn't call `GetLastPInvokeError()`. You can see [here](https://github.com/dotnet/runtime/blob/318018e48c8aebd4feb247caadc400b15c83498e/src/libraries/System.Private.CoreLib/src/System/ComponentModel/Win32Exception.cs#L31) that if you give it an error code, it calls `GetPInvokeError(error)` (no "Last"). That [calls `Interop.Kernel32.GetMessage(error)`](https://github.com/dotnet/runtime/blob/ee5b738d482b2bf72a5d6aab486e770490ade194/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.Windows.cs#L265), which... – Gabriel Luci Dec 11 '22 at 23:49
  • 1
    then ends up calling the native Windows [`FormatMessageW`](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessagew). – Gabriel Luci Dec 11 '22 at 23:49
  • 1
    @VictorioBerra: I see the "get error message from Win32Exception" as a stable way to translate a numeric code into a message. The concern in my question is more about extracting the code from an error string. – LeGEC Dec 12 '22 at 05:02
  • @LeGEC How is that ErrorMessage built inside S.D.P? If the source code always formats the ErrorMessage with the hex code in the beginning then I would think it would be fine to parse it out that way. – Victorio Berra Dec 12 '22 at 15:26
  • The Response has internally an XmlNode dsmlNode which contains the error node as well. I do not think it will get more direct than that. You need to know what in this xml can be contained. DSML is an standard: https://vdocuments.net/dsml-v20-specification.html?page=6 where the errornode has no fixed format. You will be bound to parse the contents and deal with other LDAP providers which can fill in different things in case of error. – Alois Kraus Dec 14 '22 at 23:58

1 Answers1

1

Coming back to this question :

I realized that going through a standard LDAP layer to communicate with the server would only get me a standard LDAP Result, e.g a struct with basically one LDAP error code (the resultCode field) and a message string (the diagnosticMessage field).

Perhaps Active Directory could have added controls in its response that would indicate AD specific informations, but it looks like it doesn't.

So all in all, I don't think there can be a much better way to decode that error message : a library would probably just apply a similar treatment on the message string alone, and hope that it is decoding a message coming from an Active Directory -- not a message coming from an OpenLDAP or Novell or whatnot implementation, that happens to start with a valid 8-char hex code.

In our case: since our requests also included controls heavily linked to AD (DirSync controls), the confidence of talking to an Active Directory was basically 100%.


Instead of going through an LDAP connection, I could perhaps have gone through a Win32 API specific function, which could return the Win32 error code as a value, but I haven't had the opportunity to test this when we were faced with this issue.


for reference, a list of possible Win32 error codes is listed here

LeGEC
  • 46,477
  • 5
  • 57
  • 104