3

I am attempting to write a utility method to update AD attributes (just single valued string attributes for now) in C#. This is a stand-alone utility that does not rely on IIS. This method will be used to load data from our HR systems into our AD.

I am able to read objects and attributes effectively using System.DirectoryServices.Protocols. but when I call the ModifyRequest method, I get a DirectoryOperationException with the message "The server cannot handle directory requests".

Based on another Stack Overflow question: .Net's Directory Services throws a strange exception

I tried using port 636 for SSL LDAP, but it does not change the behavior.

I am not using IIS and am on .NET 4.5 so the Microsoft patch for .NET/IIS should not apply.

Googling on this has been fruitless.

If know you why this error occurs, and how to fix it, I would be very grateful.

Code below.. please assume that Conn contains a valid and authenticated LDAP connection from the enclosing utility class--I can provide the complete source the enclosing utility class if it is needed.

The exception occurs on the SendRequest line in ModifyStringAttributeValues:

using System;
using System.Collections.Generic;
using System.DirectoryServices.Protocols;
using System.Net;

namespace MyOrganization.Common.Ldap
{
    public class LdapSession
    {
        public bool UseKerberos { set; get; }
        public String Host { set; get; }
        public String UserId { set; get; }
        public String Password { set; get; }
        public String Tag { set; get; }
        public int Port { set; get; }

        public const int DefaultLdapPort = 389;

        protected LdapConnection Conn;

        public void EstablishV2()
        {

        }

        public void Establish()
        {

            var effectivePort = Port == 0 ? DefaultLdapPort : Port;

            Console.WriteLine("EffectivePort={0}", effectivePort);

            var identifier = new LdapDirectoryIdentifier(Host, effectivePort);

            if (UseKerberos)
            {
                Conn = new LdapConnection(identifier)
                {
                    AuthType = AuthType.Kerberos,
                    Timeout = new TimeSpan(0, 10, 0, 0),
                    SessionOptions =
                    {
                        ProtocolVersion = 3,
                        VerifyServerCertificate =
                            new VerifyServerCertificateCallback((con, cer) => true),
                        SecureSocketLayer = true
                    }
                };
            }
            else
            {
                Conn = new LdapConnection(identifier)
                {
                    AuthType = AuthType.Basic,
                    Timeout = new TimeSpan(0, 10, 0, 0)
                };

                // Console.WriteLine("LPA:  Binding with {0}, {1}", UserId, Password); // QUARTZ

                Conn.Bind(new NetworkCredential(UserId, Password));
            }


        }

        public IEnumerable<SearchResultEntry> Search(string cx, string filter, SearchScope searchScope, params string[] attrib)
        {
            var s = new SearchRequest(cx, filter, searchScope, attrib)
            {
                SizeLimit = 0,
                TimeLimit = new TimeSpan(1, 0, 0) // One hour, zero minutes, zero seconds
            };

            var raw = Conn.SendRequest(s);

            if (raw == null)
            {
                throw new Exception("null response");
            }

            var r = raw as SearchResponse;

            if (r != null)
            {
                // Console.WriteLine(Tag + "Search response entries: {0}", r.Entries.Count); // QUARTZ

                foreach (SearchResultEntry e in r.Entries)
                {
                    yield return e;
                }
            }
            else
            {
                // Console.WriteLine(Tag + "Search response was null" ); // QUARTZ
            }

            yield break;
        }


        public ResultCode ModifyStringAttributeValues(string dn, IDictionary<string, string> modifications)
        {
            // declare the request and response objects here
            // they are used in two blocks
            ModifyRequest modRequest;
            ModifyResponse modResponse;

            try
            {
                // initialize the modRequest object 
                modRequest =
                    new ModifyRequest(dn);

                modRequest.Controls.Add(new PermissiveModifyControl());

                var mods = new DirectoryAttributeModification[modifications.Count];

                int z = 0;
                foreach (var pair in modifications)
                {
                    var mod = new DirectoryAttributeModification();
                    mod.Operation = DirectoryAttributeOperation.Replace;
                    mod.Name = pair.Key;
                    mod.Add(pair.Value);

                    mods[z] = mod;

                    z += 1;
                }

                // cast the returned directory response into a ModifyResponse type 
                // named modResponse
                modResponse =
                    (ModifyResponse)Conn.SendRequest(modRequest);

                return modResponse.ResultCode;
            }

            catch (Exception e)
            {
                Console.WriteLine("\nUnexpected exception occured:\n\t{0}: {1}",
                                  e.GetType().Name, e.Message);

                return ResultCode.Unavailable;
            }
        }
    }
}

I know the code is a little clunky, and full of strange comments--it is cut, pasted and modified from sample code on Microsoft's site while I get it working.

Community
  • 1
  • 1
SAJ14SAJ
  • 1,698
  • 1
  • 13
  • 32
  • I was also wondering why you need the PermissiveModifyControl. Have you tried without it ? http://stackoverflow.com/q/3450732/1236044 – jbl Nov 23 '12 at 13:18
  • Did you find the answer to your problem ? (Finishing a C# - redhat LDAP DS and I might have an AD LDAP project coming-in, so i'm gathering some info ;-) Some people advice to raise, if possible, the log level on the AD server as "The server cannot handle directory requests" seems like a generic AD error – jbl Nov 28 '12 at 09:44

4 Answers4

3

If someone runs into this problem again, here is my solution. It was the action of deleting duplicate user certificates which resolved it for me. Here are the steps

  1. Run > certmgr.msc
  2. Go to the personal folder and find the related certificates
  3. Finally delete any duplicated certificates
ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
ahaliav fox
  • 2,217
  • 22
  • 21
2

I got this issue too, probably hardening the domain was the cause.

The solution was to call with the negotiation settings of a domain instead of simple. This is probably a bug in ValidateCredentials since it is only checked for LdapException. See https://github.com/dotnet/runtime/blob/main/src/libraries/System.DirectoryServices.AccountManagement/src/System/DirectoryServices/AccountManagement/Context.cs in Validate function of internal class CredentialValidator.

Code which should work: ctx.ValidateCredentials(username, password, ContextOptions.Negotiate | ContextOptions.Sealing | ContextOptions.Signing);

Additional, maybe correct information:

https://living-sun.com/143080-how-do-i-validate-active-directory-creds-over-ldap-ssl-c-net-active-directory-ldap-directoryservices.html

Edit: It should be fixed in .NET 6.0: https://github.com/dotnet/runtime/commit/ce95c98fbbf7592b1b74127a4b87fcd607a25c1a#diff-5ec762bfbfb222fe7cb9500c16e615227b7ab971f4118bb9be168c6a4716683e

Trivalik
  • 80
  • 7
1

My issue had to do with the attribute value not satisfying a restraint. I was attempting to set a password on an account without meeting all of the requirements (Uppercase, include a number, etc...)

robbie
  • 658
  • 3
  • 11
  • 26
0

It might be a problem of server certificate checking (if your server is auto-signed for example)

Here is some test-code to establish a SSL connection bypassing of the server certificate checking :

    public static LdapConnection GetLdapConnection(string login, string password)
    {
        var serverName = /*myServerNameFromConfig*/;
        var port = /*myPortFromConfig*/;
        LdapDirectoryIdentifier ldi = new LdapDirectoryIdentifier(string.Format("{0}:{1}", serverName, port));

        NetworkCredential nc = new NetworkCredential(login, password);

        LdapConnection connection = new LdapConnection(ldi, nc, System.DirectoryServices.Protocols.AuthType.Basic);
        connection.SessionOptions.ProtocolVersion = 3;
        connection.SessionOptions.VerifyServerCertificate =
                new VerifyServerCertificateCallback((con, cer) => true);
        connection.SessionOptions.SecureSocketLayer = true;
        return connection;
    }
jbl
  • 15,179
  • 3
  • 34
  • 101
  • 1
    Thank you... my turkey is in the oven right now... but I will try this tomorrow. – SAJ14SAJ Nov 22 '12 at 21:45
  • I updated the authentication part of the class (whose complete code is now in the question, it is not that long) to include the SessionOptions you recommended. This is found in the `Establish` method. There is no change in behavior. (Note that this class is intended for Microsoft and non-Microsoft LDAP sources--this use case has the `UseKerberos` option set to true, and the `Port` set to 636). – SAJ14SAJ Nov 23 '12 at 12:59
  • @SAJ14SAJ ok, thx for the follow-up. I'll try to delete my answer as it is irrelevant. So your question will appear as unanswered and hopefully someone will give a try. – jbl Nov 23 '12 at 13:16
  • I wouldn't delete your answer, it was an honest try, and I learned something from it. I did the `UsePermissive` because the Microsoft docs on the class indicated that it more tolerant of conditions where the attribute may or may not exist, and has no downside for my use case. – SAJ14SAJ Nov 23 '12 at 13:18