LDAPImport of MS Active Dir with AccountExpires flag

Hi

In case this helps anyone else out:

We have several AD challenges. One is subdomains – we have domain controllers in other parts of the world, each containing the users in that part of the world. Another is that we more often expire accounts rather than disabling them. Lastly, we use AD accounts for all sorts of stuff, so we needed an attribute in AD that would identify accounts as being a real, honest-to-goodness human being (vs service account, etc).

Starting with the last one first (“human being” attribute):
We only assign EmployeeID to real people, so my LDAP (import) Filter in RT_SiteConfig.pm looks like this:
Set($LDAPFilter, ‘(&(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(EmployeeID=*))’);

The ‘not disabled’ userAccountControl works, but still picks up “expired” users.
The accountExpires field can be one of three values:
- An expiry date (as a 64-bit number representing number of 100-nanosecond ticks since 12:00 AM January 1, 1601)
- 9,223,372,036,854,775,807 (translates to ‘Never’)
- 0 (also translates to ‘Never’, and set if there was an expiration date but it was later removed).

To see if someone’s account has Expired, you would have to check the accountExpires ‘date’ against now (anything older/previous to now has expired), but account for 0 too.
This is what I ended up adding to LDAPImport.pm:

diff -wu LDAPImport.pm.orig LDAPImport.pm

@@ -417,11 +417,25 @@

sub run_user_search {
my $self = shift;

  •   #       ADD TO THE FILTER (IF NOT ALREADY THERE) ANOTHER RESTRICTION:
    
  •   #       THE ACCOUNT EXPIRY DATE/TIME OCCURS IN THE FUTURE (OR IS 0)
    
  • my $NOW = sprintf(“%.0f”, (time()+11644473600)*10000000);
  • my $filter = $RT::LDAPFilter =~ m/accountExpires/
  •        ? $RT::LDAPFilter
    
  •        : "(&$RT::LDAPFilter(|(accountExpires=>$NOW)(accountExpires=0)))";
    
    $self->_run_search(
    base => $RT::LDAPBase,
  •    filter => $RT::LDAPFilter
    
  •    filter => $filter
    
    );
    +}

Of note: The AccountExpires attribute in AD is not exposed at the GC level. You can check a box to allow it to sync with the GC’s or you can do your imports from DCs. My issue with going to the DCs is that I have multiple domains, so I need to run the import against each one in turn.

Which leads to the next modification I made.
I need to iterate through each of our domain controllers, but found that the ldapimport would cache connections, making this difficult.
There is undoubtedly a much Better Way™ to do this, but this works for me:
Instead of running sbin/rt-ldapimport from cron, I just call a wrapper:

#!/usr/bin/perl
use strict;
use warnings;

THIS IS SIMPLY A WRAPPER.

THIS WRAPPER JUST RUNS THROUGH THAT LIST WITHOUT CACHING THE SERVER CONNECTIONS

$|++;
my @LDAPHosts = (
dc1.example.com’,
dc2.sub1.example.com’,
dc3.sub2.example.com’,
);
# IMPORT USERS:
foreach my $host (@LDAPHosts) {
/opt/rt4/sbin/rt-ldapimport --no-groups --import --LDAPHost $host --debug;
}
# IMPORT GROUPS:
/opt/rt4/sbin/rt-ldapimport --no-users --import --debug;

You could instead put the domain controllers in RT_SiteConfig.pm (vs hard coding like above):
Set($LDAPHosts, [
dc1.example.com’,
dc2.sub1.example.com’,
dc3.sub2.example.com’,
]
);