JNDI, Active Directory and User Account status (account expired, locked)
Mar 9, 2006 11:37 AM
I figure enough people are confused that this topic deserves a post of it's own.
Account Expires
An account can be set to expire automatically, by simply assigning a valid expiration date to the account. What this means is that a user, even though they may have valid credentials, will not be able to logon with that account.
The attribute is called accountExpires and it contains a Win32 time value (I used to think it was the number of milliseconds since 1/1/1601, but there seems to be a discrepency between the Java time epoch which is based on number of milliseconds since 1/1/1970 and the Win32 units. Perhaps Win32 is measured in tenth of nanoseconds, perhaps the Java docs are wrong, or perhaps I'm wrong ?).
To determine whether an account expires today, calculate the number of millisecond/nanosecond units since 1/1/1601 and check whether this value is greater than the value of the accountExpires attribute.
//Specifiy the LDAP search filter
GregorianCalendar Win32Epoch = new GregorianCalendar(1601,Calendar.JANUARY,1);
Date Win32EpochDate = Win32Epoch.getTime()
//Note that 1/1/1601 will be returned as a negative value by Java
GregorianCalendar Today = new GregorianCalendar();
Date TodaysDate = Today.getTime();
long TimeSinceWin32Epoch = 10000 * (TodaysDate.getTime() - Win32EpochDate.getTime());
String searchFilter = "(&(objectClass=User)(accountExpires<=" + TimeSinceWin32Epoch + ")(accountExpires>=1))";
To set an account to not expire, set the value of accountExpires to zero. Hence the reason to check that accountExpires is non zero.
Account Locked
An account may be automatically locked, if the domain's security policy has been configured to lock accounts after a number of unsuccessful logon attempts. If an account has been locked out, the lockoutTime attribute will contain a Win32 time value that indicates when the account was locked.
An easy way to search for locked out accounts is an LDAP query similar to (&objectClass=user)(lockoutTime=*)) Eg. Search for any accounts that have a value for lockoutTime. However this 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 the system would set 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.
You obtain the value of lockoutDuration from the domainDNS object
....
Attributes attrs = ctx.getAttributes("dc=antipodes,dc=com");
System.out.println("Account Lockout Policy for " + attrs.get("distinguishedName").get());
System.out.println("Threshold " + attrs.get("lockoutThreshold").get());
System.out.println("Duration " + attrs.get("lockoutDuration").get());
//Save the duration for later calculations
long lockoutDuration = Long.parseLong(attrs.get("lockoutDuration").get().toString());
....
SearchControls searchCtls = new SearchControls();
//Specify the attributes to return
String returnedAtts[]={"sn","givenName"};
//Specify the search scope
searchCtls.setSearchScope(searchControls.SUBTREE.SCOPE);
//specify the filter
GregorianCalendar Win32Epoch = new GregorianCalendar(1601,Calendar.JANUARY,1);
Date Win32EpochDate = Win32Epoch.getTime()
GregorianCalendar Today = new GregorianCalendar();
Date TodaysDate = Today.getTime();
long TimeSinceWin32Epoch = 10000 * (TodaysDate.getTime() - Win32EpochDate.getTime());
String searchFilter = "(&(objectClass=user)(lockoutTime>=" + (TimeSinceWin32Epoch + lockoutDuration) + "))";
Note to unlock an account, just set the value of the attribute lockoutTime to zero
Password Expires
Similar to above but just need to retrieve the maxPwdAge from the domainDNS object and perform a similar comparison to the user's pwdLastSet atribute
To force a password to expire (eg. the user must change it at the next logon, set the value of pwdLastSet to zero. To "unexpire" the password set the value to -1 which implies the current time. Values other than 0 or -1 are not permitted.
Account Disabled
Accounts may be disabled by setting the second bit of the userAccountControl attribute. For code samples refer to http://forum.java.sun.com/thread.jspa?threadID=588430&messageID=3045217
Note:
For Active Directory Application Mode (ADAM, now referred to as Active Directory Lightweight Directiry Service) there are constructed boolean attributes that simplify some of these operations. These include:
msDS-UserAccountDisabled, ms-DS-UserAccountAutoLocked, msDS-UserPasswordExpired, msDS-User-Account-Control-Computed (an enumerated value for account & password expiration).
Active Directory schema definitions can be found at http://msdn.microsoft.com/library/en-us/adschema/adschema/active_directory_schema.asp
I am having some issues with getting expired passwords to work.
I have followed the outline from checking if the account is locked (which works correctly), but I keep getting incorrect return values. Accounts are showing that the pasword is expired, but I physically watched the user change his/her password, and have also changed the password through the mmc, but it still shows up as expired.
Here is the code I used for checking if the account is lockedout and if the password is disabled
.
.
.
long lockoutDuration = Long.parseLong(attrsDomain.get("lockoutDuration").get().toString());
//query for password expiration period
long pwdexpireDuration = Long.parseLong(attrsDomain.get("maxPwdAge").get().toString());
//setup the calander for calculation
GregorianCalendar Win32Epoch = new GregorianCalendar(1601,Calendar.JANUARY,1);
Date Win32EpochDate = Win32Epoch.getTime();
GregorianCalendar Today = new GregorianCalendar();
Date TodaysDate = Today.getTime();
long TimeSinceWin32Epoch = 10000 * (TodaysDate.getTime() - Win32EpochDate.getTime());
.
.
.
//locked/unlocked
long userLockoutTime = 0;
try{
userLockoutTime = Long.parseLong((String)attrs.get("lockoutTime").get());
}
catch(Exception e){
System.out.println("Account has never been locked out, setting to 0 to cheat it");
userLockoutTime = 0;
}
if ( userLockoutTime >= (TimeSinceWin32Epoch + lockoutDuration))
{
tempuser.isLockedaccount(true);
}
else {
tempuser.isLockedaccount(false);
}
//expired password
long userpwdChangeTime;
userpwdChangeTime = Long.parseLong((String)attrs.get("pwdLastSet").get());
if ( userpwdChangeTime >= (TimeSinceWin32Epoch + pwdexpireDuration))
{
tempuser.isExpiredpw(true);
}
else {
tempuser.isExpiredpw(false);
}
I am truely confused to why when my code says the password is expired, the use can log in fine and not get the "change your password" window.
I am also confused as the maxPwdAge value from the domain is a rather large negative number...
Anyone see what I am doing wrong, or any thoughts on this??
Re: JNDI, Active Directory and User Account status (account expired, locked
May 1, 2006 3:40 AM
(reply 2
of 16) (In reply to
#1 )
maxPwdAge IS a large negative number.
It is stored as the negative number of 100 nanosecond intervals.
These snippets may help you:
Determing domain policy settings
Attributes attrs = ctx.getAttributes("dc=antipodes,dc=com");
System.out.println("Password Policy for " + attrs.get("distinguishedName").get());
System.out.println("Password Age: " + attrs.get("maxPwdAge").get());
Long pwdAge = new Long(attrs.get("maxPwdAge").get().toString());
System.out.println("Password Age (Days): " + ((pwdAge/-86400)/10000000)); //stored as -ve number of hundred nanosecond intervals Eg. 10^7
System.out.println("Min Password Length: " + attrs.get("minPwdLength").get());
Determing the LDAP searchfilter
//calculate the password expiration date
GregorianCalendar Win32Epoch = new GregorianCalendar(1601,Calendar.JANUARY,1);
GregorianCalendar Today = new GregorianCalendar();
Long Win32EpochInMillis = Win32Epoch.getTimeInMillis();
Long TodayInMillis = Today.getTimeInMillis();
Long Expiration = (TodayInMillis - Win32EpochInMillis) * 10000;
System.out.println("Today's Date: " + DisplayWin32Date(Expiration.toString());
Expiration = Expiration + pwdAge;
System.out.println("Expiration Date: " + DisplayWin32Date(Expiration.toString());
//Specify the search filter
String searchFilter = ((&(objectClass=user)(pwdLastSet<=" + Expiration + "))";
A useful function for displaying Win32 Date/Time
static String DisplayWin32Date(String Win32FileTime) {
GregorianCalendar Win32Epoch = new GregorianCalendar(1601,Calendar.JANUARY,1);
Long lWin32Epoch = Win32Epoch.getTimeInMillis();
Long lWin32FileTime = new Long(Win32FileTime);
return(DateFormat.getDateTimeInstance(DateFormat.FULL,DateFormat.FULL).format((lWin32FileTime/10000) + lWin32Epoch));
Note that a password may have expired, but the user may not be prompted to change their password if the userAccountControl is set to not expire the password. Also note that the Active Directory stores date/time as UTC, so your application may need to take into consideration different timezones and/or daylight savings time.
Re: JNDI, Active Directory and User Account status (account expired, locked
Jun 15, 2006 1:28 PM
(reply 5
of 16) (In reply to
#4 )
Hey Thanks guys,
This code helped sove my problem
My value from AD is lasppwdset =127948319499226601
long pwdLastSet = Long.parseLong("127948319499226601");
System.out.println("long value : "+pwdLastSet);
long javaTime = pwdLastSet - 0x19db1ded53e8000L;
javaTime /= 10000L;
Date today = new Date(javaTime);
System.out.println("java DATE value : "+today);
SimpleDateFormat sdf2 = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
String newDateString = sdf2.format(today);
System.out.println("Date When Password Last Set :"+newDateString);
GregorianCalendar cal0=new GregorianCalendar();
cal0.setTime(today);
cal0.add(5, 385);
newDateString = sdf2.format(cal0.getTime());
System.out.println("Date When Password Expires :"+newDateString);
GregorianCalendar cal = (GregorianCalendar)Calendar.getInstance();
cal.add(5, 385);
cal.getTime();
/***
* Fine till above line getting expected Value in java.util.date
*/
long pwdAge = new Long("-332640000000000").longValue();
System.out.println("Password Age (Days): " + ((pwdAge/-86400)/10000000)); //stored as -ve number of hundred nanosecond intervals Eg. 10^7
GregorianCalendar Win32Epoch = new GregorianCalendar(1601,Calendar.JANUARY,1);
GregorianCalendar Today = new GregorianCalendar();
long Win32EpochInMillis = Win32Epoch.getTimeInMillis();
long TodayInMillis = Today.getTimeInMillis();
long Expiration = (TodayInMillis - Win32EpochInMillis) * 10000;
System.out.println("Today's Date: " + Expiration);
Expiration = Expiration + pwdAge;
System.out.println("Expiration Date: " + Expiration);
Hi
I am tring to find out a user is currently lockedout or password expired from MS Active Directory. I used the codes to write my methods for checking. but at the end one question comeing up to my mind on this code.
how correct is it to get current time in java from system that runs jvn rather than active directory's. because all the times entered in user attributes are in refference to active direcotries system date and we are compairing it with jvm's system date which might be two different m/c.
if someone agrees might concerned please provide solutions to may be reading datetime from active directory.
Re: JNDI, Active Directory and User Account status (account expired, locked)
Jul 26, 2006 9:38 AM
(reply 8
of 16) (In reply to
#7 )
Hi Steve,
Infact i'm trying to query AD for the status of an account like a/c locked and password expired. As you mentioned in the starting section of this thread, i tried to retrieve lockoutDuration and lockoutThreshold. But unfortunately i couldn't find those variables.
When i browse the AD through a LDAP browser, i couldn't see these attributes. In this situation what 'd i do to get the accounts which are really locked ? I tried to search by lockOutTime > 0, but many accounts satisfying this criteria doesn't seems to be locked, they are disabled.
Re: JNDI, Active Directory and User Account status (account expired, locked
Jul 28, 2006 3:13 AM
(reply 9
of 16) (In reply to
#5 )
Hi pandu,
In your message reply, you've used one value (0x19db1ded53e8000L) to find out the java time. May i know how do you get this value ? After caluculation are you getting the time in java, corresponding to the pwdLastSet time ?
I'm just now working with AD. Since new, i've got few doubts.
Re: JNDI, Active Directory and User Account status (account expired, locked)
Jul 28, 2006 8:07 AM
(reply 10
of 16) (In reply to
#8 )
Sorry, I'm only guessing here, but I think that the only reasons why you may not be able to retrieve the values for lockoutDuration and lockoputThreshold are:
a. No group policy settings have been configured for account lockout, or
b. The account you are using does not have permission to read the values
If the first reason applies, then accounts won't get locked out, so the question is moot (although I would suggest that this is a bad security policy, allowing your directory to be targetted by dictionary attacks).
If the latter reason is the cause, then you may have to hardcode the values in your app, rather than obtain them programmatically. However more importantly, it is also likely that you may not have permission to retrieve the value of lockoutTime from the user objects, so again the question becomes moot.
BTW, Are you using Active Directory, or Active Directory Application Mode (ADAM) ?
If you are using ADAM, it will derive it's password & account policy from the server's policy. If the server is a member of a domain, it will derive its policy from the domain. If it is a stand-alone server, it uses its own policy. And if ADAM is running on XP, then no account or password policies are applied.
Re: JNDI, Active Directory and User Account status (account expired, locked)
Jul 31, 2006 7:17 AM
(reply 11
of 16) (In reply to
#10 )
Hi Steve,
First of all, let me answer your last question. I'm using Active Directory, not ADAM, thats for sure.
Infact, one service account has been created for our use, we access AD through this account. Hope your guessing about enough permission is true, i think.
Moreover i'm not using JNDI, instead i use direct LDAPConnection to the server. Will that make any problem ? Below is my code, would you just try to point out the problem. Due to security problems, i've deleted few lines, but you can understand the logic.
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import com.novell.ldap.LDAPAttribute;
import com.novell.ldap.LDAPAttributeSet;
import com.novell.ldap.LDAPConnection;
import com.novell.ldap.LDAPControl;
import com.novell.ldap.LDAPException;
import com.novell.ldap.LDAPSearchConstraints;
import com.novell.ldap.LDAPSearchResults;
publicclass ADLookupUtil {
privatefinalint LDAP_AUTH_SUCCESS = 0;
privatefinalint LDAP_AUTH_FAIL = -1;
privatefinalint INVALID_CREDENTIAL = 49;
private LDAPConnection connection = null;
privatefinalint UF_ACCOUNTDISABLE = 2;//0x0002
privatefinalint UF_LOCKOUT = 16;//0x0010
privatefinalint UF_PASSWORD_EXPIRED = 8388608;//0x800000
privatefinalint UF_DONT_EXPIRE_PASSWD = 65536;//0x00010000
privatefinalint UF_NORMAL_ACCOUNT = 512;//0x0200
String searchAttribs[] = {"userAccountControl","lockoutTime","pwdLastSet","logonCount"};
/**
* Constructor
* Making a LDAP Connection
*/
public ADLookupUtil(){
connection = new LDAPConnection();
}
/**
* Function tries to connect to Active Directory
* @param host Active Directory server name
* @param port the port
*/
privateboolean connect(String host, String port) {
try {
connection.connect(host, new Integer(port).intValue());
returntrue;
} catch (LDAPException ldapex) {
}
returnfalse;
}
/**
* Function to disconnect from Active Directory
*
*/
privatevoid disconnect() {
try {
if (connection.isConnected()) {
connection.disconnect();
}
} catch (LDAPException ldapex) {
}
}
/**
* Function to authenticate the user to Active directory with user credentials
* @param baseDN the baseDN
* @param passwd the password
* @return
*/
privateint authenticate(String baseDN, String passwd) {
try {
try {
connection.bind(LDAPConnection.LDAP_V3, baseDN.toString(), passwd.getBytes("UTF8"));
} catch (UnsupportedEncodingException e) {
disconnect();
return LDAP_AUTH_FAIL;
}
return LDAP_AUTH_SUCCESS;
} catch (LDAPException ldapex) {
disconnect();
}
return LDAP_AUTH_FAIL;
}
/**
* Function to look up the active directory when corpId is supplied
* @param corpId the unique corpId of the person calling
* @return the account status object
*/
public ActiveDirectoryResponse lookupEmployee(String corpId, Employee employee) {
ActiveDirectoryResponse adr = new ActiveDirectoryResponse();
ApplicationContext context = ApplicationContext.getInstance();
HashMap searchResult = new HashMap();
int adReturnValue = -1;
String searchFilter = "sAMAccountName=".concat(corpId);
try{
adr = new ActiveDirectoryResponse();
searchResult =lookupAD(context.getProperty(HOST),
context.getProperty(PORT),
context.getProperty(BASE_DN),
context.getProperty(PASSWORD),
context.getProperty(SEARCH_BASE),
searchFilter, LDAPConnection.SCOPE_SUB);
try{
if(searchResult.size() == 0){
searchResult =lookupAD(context.getProperty(ANOTHER_HOST),
context.getProperty(ANOTHER_PORT),
context.getProperty(ANOTHER_BASE_DN),
context.getProperty(ANOTHER_PASSWORD),
context.getProperty(ANOTHER_SEARCH_BASE),
searchFilter,LDAPConnection.SCOPE_SUB);
}
}catch(LDAPException lde){
}
}catch(LDAPException lde){
}
if(searchResult.size() >0){
try{
//Checks whether user account is disabled
long userAccountControl = new Long(searchResult.get(searchAttribs[0]).toString()).longValue();
if((userAccountControl & UF_ACCOUNTDISABLE)== UF_ACCOUNTDISABLE){
adr.setAcDisabled(true);
try{
searchResult =lookupAD(context.getProperty(ANOTHER__HOST),
context.getProperty(ANOTHER_PORT),
context.getProperty(ANOTHER_BASE_DN),
context.getProperty(ANOTHER_PASSWORD),
context.getProperty(ANOTHER_SEARCH_BASE),
searchFilter,LDAPConnection.SCOPE_SUB);
}catch(LDAPSearchException lde){
(searchResult.size() >0){
userAccountControl = new Long(searchResult.get(searchAttribs[0]).toString()).longValue();
if((userAccountControl & UF_ACCOUNTDISABLE)== UF_ACCOUNTDISABLE){
adr.setAcDisabled(true);
return adr;
}else{
adr.setAcDisabled(false);
}
}else{
return adr;
}
}
//Checks whether the user's accout is locked out
if(searchResult.containsKey(searchAttribs[1].toString())){
long lockoutTime = new Long(searchResult.get(searchAttribs[1]).toString()).longValue();
if(lockoutTime >0){
adr.setAcLocked(true);
}
}
//Checks whether the user is a new hire
if(searchResult.containsKey(searchAttribs[2].toString())){
long logonCount = -1;
if(searchResult.containsKey(searchAttribs[3].toString()))
logonCount = new Long(searchResult.get(searchAttribs[3]).toString()).longValue();
else
logonCount = 0;
//long pwdLastSet = new Long(searchResult.get(searchAttribs[2]).toString()).longValue();
long pwdLastSet = Long.parseLong(searchResult.get(searchAttribs[2]).toString());
if(pwdLastSet != 0){
pwdLastSet -= 0x19db1ded53e8000L;//the difference Win32 date(1/1/1601) and java date(1/1/1970)
pwdLastSet /= 10000;
}
pwdLastSet = new Date(pwdLastSet).getTime();
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S",new DateFormatSymbols(Locale.US));
Date hireDate = (Date)formatter.parse(employee.getHireDate());
System.out.println(" Password Last set on :"+formatter.format(new Date(pwdLastSet)));
userAccountControl = new Long(searchResult.get(searchAttribs[0]).toString()).longValue();
if(((pwdLastSet == 0)
||(pwdLastSet < hireDate.getTime()))
&&(logonCount == 0)
&&(userAccountControl & UF_DONT_EXPIRE_PASSWD) != UF_DONT_EXPIRE_PASSWD){
adr.setIsNewHire(true);
}
}
// Checks whether the user's password is expired
if((userAccountControl & UF_PASSWORD_EXPIRED) == UF_PASSWORD_EXPIRED){
adr.setAcExpired(true);
}
}catch(Exception ee){ }
}
return adr;
}
private HashMap lookupAD(String adHost,String adPort, String adBaseDN,
String adPassword, String searchBase, String searchFilter,
int searchScope) throws LDAPException {
CSCWebAppTrace.entering(this, "lookupAD");
Long attributeValue = null;
Iterator allAttributes=null;
Enumeration allValues=null;
LDAPAttribute attribute=null;
LDAPAttributeSet attributeSet=null;
String sAttrName, sAttrValue = null;
LDAPSearchResults searchResults = null;
HashMap searchResult = new HashMap();
try{
connect(adHost,adPort);
}catch(ADConnectionException eadc){
CSCWebAppTrace.debug(this,eadc.getMessage());
return searchResult;
}
try{
authenticate(adBaseDN, adPassword);
}catch(ADBindException eadb){
return searchResult;
}
//Search the directory
try{
LDAPSearchConstraints srchConst = connection.getSearchConstraints();
connection.setConstraints(srchConst);
try{
searchResults = connection.search(searchBase, //search base
searchScope,//search scope
searchFilter,//"cn=",//search filter
searchAttribs,// return all attributes
false,
srchConst); //search constraints
}catch(LDAPException lde){
disconnect();
return searchResult;
}
LDAPControl[] controls = connection.getResponseControls();
while ( searchResults.hasMore()){
//Print out all the attributes for each entry
attributeSet = searchResults.next().getAttributeSet();
allAttributes = attributeSet.iterator();
while (allAttributes.hasNext()) {
attribute = (LDAPAttribute)allAttributes.next();
sAttrName = attribute.getName();
allValues = attribute.getStringValues();
attributeValue = null;
while (allValues.hasMoreElements()) {
Object obj = allValues.nextElement();
if(obj != null){
attributeValue = new Long((String)obj);
searchResult.put(sAttrName,attributeValue);
System.out.println("sAttrName :"+sAttrName+" value :"+attributeValue);
}
}
}
} // end while
} // end try
catch (Exception e) {
}
disconnect();
return searchResult;
}
}
Re: JNDI, Active Directory and User Account status (account expired, locked)
Aug 4, 2006 8:19 AM
(reply 12
of 16) (In reply to
#11 )
HI RK,
The password & account lockout policies are stored as attributes on the domainDNS object, not on each user object. It doesn't look like you are retrieving these values in your code.
For example in my domain "Antipodes.com", I retrieve the policy values from the attributes: lockoutThreshold, lockoutDuration, minPwdAge, maxPwdAge, minPwdLength which are stored on the domainDNS object which has a distinguished name "dc=antipodes,dc=com".
Note that the userAccountControl flags do not automagically get updated to correctly indicate the account lockout or password expired status. You should check the user's lockoutTime and pwdLastSet respectively.
You also mention that you are using a service account. I'm assuming that you are using Network Service. Assuming the default permissions are in place, I think it should work OK.
Good luck.
Re: JNDI, Active Directory and User Account status (account expired, locked)
Dec 5, 2006 1:18 AM
(reply 13
of 16) (In reply to
#12 )
Hi,
I have read all the posts on this thread but I am still having a problem in checking whether an account is locked out or not. Please check the snippet and let me know if there is something I'm doing wrong...
publicboolean isAccountLocked(){
//if lockoutTime flag is zero, immediately return false
if(lockoutTime == 0){
returnfalse;
}
//otherwise, determine if account is still locked out
try {
//Call to set get the lockoutDuration attribute from the domainDNS
setLockoutDuration();
} catch (LDAPException ex) {
ex.printStackTrace();
returntrue;
}
GregorianCalendar win32epoch = new GregorianCalendar(1601,Calendar.JANUARY,01);
Date Win32EpochDate = win32epoch.getTime();
GregorianCalendar Today = new GregorianCalendar();
Date TodaysDate = Today.getTime();
long TimeSinceWin32Epoch = 10000 * (TodaysDate.getTime() - Win32EpochDate.getTime());
if(lockoutTime >= (TimeSinceWin32Epoch + lockoutDuration)){
returntrue;
}
returnfalse;
}
I am using the netscape.ldap package to connect to the AD, the value I get for lockoutDuration is -18000000000. As you can see the code is similar to the code posted earlier by you. When I test this code is always returns false even for accounts I have intentionally locked out.