Software Architecture – To find lockedout users from active directory

Objective : To find lockedout users from active directory

General Implementation:
Generally LDAP filter for getting Lockout accounts list is as:
(&(objectCategory=Person)(objectClass=user)(lockoutTime:1.2.840.113556.1.4.804:=4294967295))
Where 4294967295 is the highest number you can get using 32 bits (Binary digits)
11111111111111111111111111111111 = 4,294,967,295
FFFFFFFF = 4,294,967,295

1.2.840.113556.1.4.804 is the LDAP_MATCHING_RULE_BIT_OR rule. The matching rule is true if any bits from the property match the value. This rule is like the bitwise OR operator.

LockoutTime is AD attribute which is the date and time (UTC) that this account was locked out. This value is stored as a large integer that represents the number of 100-nanosecond intervals since January 1, 1601 (UTC). A value of zero means that the account is not currently locked out.
So (lockoutTime:1.2.840.113556.1.4.804:=4294967295) means fetch those entries which has any date time value in form of large integer.
Problem:
However searching for any accounts that have a value for lockouttime is not an accurate method to use because an account is determined to be locked out if the CurrentTime – lockouttime exceeds the Lockout Duration.
It is only upon a successful logon that AD sets the value of lockouttime to zero, so it is possible for an account to still contain a value for lockouttime, yet the account is not locked.

Solution:
The correct LDAP filters with a dynamic calculation based on Active Directory Account Lockout policy.
To determine the users that are currently locked out, you have to query the lockoutDuration attribute stored on the domain object (e.g., dc=rallencorp,dc=com). This attribute defines the number of minutes that an account will stay locked before becoming automatically unlocked. You need to take this value and subtract it from the current time to derive a timestamp that would be the outer marker for which users could still be locked. You can then compare this timestamp with the lockoutTime attribute of the user object. The search filter to find all locked users once you’ve determined the locked timestamp would look something like this:

(&(objectcategory=Person)(objectclass=user)(lockoutTime>=DerivedTimestamp))

For any users that have a lockoutTime that is less than the derived timestamp, their account has already been automatically unlocked per the lockoutDuration setting.

Where DerivedTimestamp = ( Now-LockoutDurationFromDomainPolicy)
[While accounting for local time zones and daylight savings time.]

So in this way, we can avoid problem in previous ldap filter.

Use of Windows Time Service Tool named W32tm.exe for diagnostic purpose to Convert an NT system time, in (10^-7)s intervals from 0h 1-Jan 1601, into a readable format as below.

w32tm /ntte

Code:
Class for getting the domain policy will be as:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices;

namespace MyDirectoryServices.MyDirectory
{

    /*     
     This class get the domain poilcy from domain controller
     All policy attributes can be get via that.
     Make a public property and user it, as done for LockoutDuration
     Reference: http://en.csharp-online.net/User_Management_with_Active_Directory%E2%80%94Determining_Domain-Wide_Account_Policies
     */

    [Flags]
    public enum PasswordPolicy
    {
        DOMAIN_PASSWORD_COMPLEX = 1,
        DOMAIN_PASSWORD_NO_ANON_CHANGE = 2,
        DOMAIN_PASSWORD_NO_CLEAR_CHANGE = 4,
        DOMAIN_LOCKOUT_ADMINS = 8,
        DOMAIN_PASSWORD_STORE_CLEARTEXT = 16,
        DOMAIN_REFUSE_PASSWORD_CHANGE = 32
    }

    public class DomainPolicy
    {
        public static string pLockoutTime = "lockoutTime";
        public static string pLockoutDuration = "lockoutDuration";
        public const long pNeverExpireConstant = 9223372036854775807;  // Used for AD attribute accountexpire, to show as never expires value
    
        ResultPropertyCollection attribs;

        public DomainPolicy()
        {
            // hardcoded domain name
            String pathRootDSE = String.Format("LDAP://softcom.imanami.lab");
            DirectoryEntry dse = new DirectoryEntry(pathRootDSE);
            
            //properties to fetch
            string[] policyAttributes = new string[] {
                  "lockoutDuration", "objectClass", 
                      "distinguishedName"
                        };

            DirectorySearcher ds = new DirectorySearcher(
              dse,
              "(objectClass=domainDNS)",
              policyAttributes,
              SearchScope.Base
              );

            // search
            SearchResult result = ds.FindOne();

            //do some quick validation...							  
            if (result == null)
            {
                throw new ArgumentException(
                  "domainRoot is not a domainDNS object."
                  );
            }

            this.attribs = result.Properties;
        }

        //for some odd reason, the intervals are all stored
        //as negative numbers. We use this to "invert" them
        private long GetAbsValue(object longInt)
        {
            return Math.Abs((long)longInt);
        }

        /// <summary>
        /// LockoutDuration of domain policy
        /// </summary>
        public TimeSpan LockoutDuration
        {
            get
            {
                string val = pLockoutDuration;
                //this should fail if not found
                var value = this.attribs[val][0];
                if (value == null)
                    return TimeSpan.MinValue;

                var result = (long)value;
                if (result == pNeverExpireConstant || result == 0)
                    return TimeSpan.MaxValue;

                var finalResult = TimeSpan.FromTicks(result);
                return finalResult;
            }
        }
    }
    
}

The code to get list of locked out accounts will be as:

 DomainPolicy dc = new DomainPolicy();
            
            // Print LockoutDuration from domain policy
            Console.WriteLine(" lockout" + dc.LockoutDuration);

            // get difference timestamp 
            DateTime dateTimeStamp = DateTime.Now.Subtract(-dc.LockoutDuration);

            // Hard coded domain name
            String pathRootDSE = String.Format("LDAP://dc=softcom,dc=imanami,dc=lab");

            DirectoryEntry dse = new DirectoryEntry(pathRootDSE);

            // list of properties to fetch
            string[] policyAttributes = new string[] {
            "distinguishedName" , "cn","lockoutTime"
                  };

            // filter to search the items
            string filterString = string.Format("(&(objectCategory=Person)(objectClass=user)(lockoutTime>={0}))", dateTimeStamp.ToFileTimeUtc().ToString());

            DirectorySearcher ds = new DirectorySearcher(
              dse,
              filterString,
              policyAttributes,
              SearchScope.Subtree
              );

            //search
            SearchResultCollection result = ds.FindAll();

            // loop n print users
            foreach (SearchResult item in result)
            {
                Console.WriteLine("Item: " + item.Properties["cn"][0].ToString() + " " + item.Properties["distinguishedName"][0].ToString() + " " + item.Properties["lockoutTime"][0].ToString());
            }
            //wait...
            Console.ReadLine();
       

Happy exploring AD related application architecture.

References:
http://msdn.microsoft.com/en-us/library/ms676843(v=vs.85).aspx
http://support.microsoft.com/kb/269181
http://technet.microsoft.com/en-us/library/cc773263(WS.10).aspx

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: