001package com.hfg.ldap.ad;
002
003import java.util.Calendar;
004import java.util.Date;
005import java.util.TimeZone;
006import java.util.regex.Matcher;
007import java.util.regex.Pattern;
008import javax.naming.AuthenticationException;
009
010import com.hfg.exception.InvalidValueException;
011import com.hfg.util.StringUtil;
012
013//------------------------------------------------------------------------------
014/**
015 Active Directory functions.
016 <div>
017 @author J. Alex Taylor, hairyfatguy.com
018 </div>
019 */
020//------------------------------------------------------------------------------
021// com.hfg XML/HTML Coding Library
022//
023// This library is free software; you can redistribute it and/or
024// modify it under the terms of the GNU Lesser General Public
025// License as published by the Free Software Foundation; either
026// version 2.1 of the License, or (at your option) any later version.
027//
028// This library is distributed in the hope that it will be useful,
029// but WITHOUT ANY WARRANTY; without even the implied warranty of
030// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
031// Lesser General Public License for more details.
032//
033// You should have received a copy of the GNU Lesser General Public
034// License along with this library; if not, write to the Free Software
035// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
036//
037// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
038// jataylor@hairyfatguy.com
039//------------------------------------------------------------------------------
040
041public class ActiveDirectory
042{
043   private static final Pattern sADAuthenticationExceptionPattern = Pattern.compile("AcceptSecurityContext error, data (\\S+?), ");
044
045   private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC");
046
047   // A Windows tick is 100 nanoseconds. Windows epoch = 1601-01-01T00:00:00Z
048   // ( 1601 was the first year of the 400-year Gregorian calendar cycle at the time Windows NT was made.)
049   // This is 11644473600 seconds before the Unix epoch 1970-01-01T00:00:00Z.
050   private static final long WIN32_EPOCH_OFFSET_MS = 11644473600L * 1000;
051
052   //---------------------------------------------------------------------------
053   /**
054    Converts an LDAP-specific generalized timestamp "YYYYMMDDHHMMSS.0Z"(ex: '20190104204258.0Z').
055    @param inValue a 'Z' form timestamp string
056    @return Java Date object
057    */
058   public static Date convertGeneralizedTimestamp(String inValue)
059   {
060      Calendar cal = Calendar.getInstance(UTC_TIME_ZONE);
061      cal.set(Calendar.YEAR, Integer.parseInt(inValue.substring(0, 4)));
062      cal.set(Calendar.MONTH, Integer.parseInt(inValue.substring(4, 6)) - 1);
063      cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(inValue.substring(6, 8)));
064      cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(inValue.substring(8, 10)));
065      cal.set(Calendar.MINUTE, Integer.parseInt(inValue.substring(10, 12)));
066      cal.set(Calendar.SECOND, Integer.parseInt(inValue.substring(12, 14)));
067      cal.set(Calendar.MILLISECOND, 0);
068
069      return cal.getTime();
070   }
071
072   //---------------------------------------------------------------------------
073   /**
074    Converts Active Directory timestamps used for pwdLastSet, accountExpires, LastLogon, LastLogonTimestamp and LastPwdSet.
075    The timestamp is the number of 100-nanoseconds intervals since Jan 1, 1601 UTC.
076    @param inValue timstamp as an 18-digit string
077    @return Java Date object
078    */
079   public static Date convertWin32EpochTimestamp(String inValue)
080   {
081      Date date;
082      try
083      {
084         long ticksSinceWin32Epoch = Long.parseLong(inValue);
085
086         date = new Date((ticksSinceWin32Epoch / 10000) - WIN32_EPOCH_OFFSET_MS);
087      }
088      catch (Exception e)
089      {
090         throw new InvalidValueException("The input timestamp " + StringUtil.singleQuote(inValue) + " is not in the expected format!");
091      }
092
093      return date;
094   }
095
096   //---------------------------------------------------------------------------
097   public static String decodeAuthenticationException(AuthenticationException inException)
098   {
099      // javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C0903D9, comment: AcceptSecurityContext error, data 52e, v2580 ]
100      String msg = null;
101      if (inException != null
102            && inException.getMessage() != null)
103      {
104         Matcher m = sADAuthenticationExceptionPattern.matcher(inException.getMessage());
105         if (m.find())
106         {
107            /*
108            525 user not found
109            52e invalid credentials
110            530 not permitted to logon at this time
111            531 not permitted to logon at this workstation
112            532 password expired
113            533   account disabled
114            534 The user has not been granted the requested logon type at this machine
115            701 account expired
116            773 user must reset password
117            775 user account locked
118             */
119            if (m.group(1).equals("525"))
120            {
121               msg = "User not found";
122            }
123            else if (m.group(1).equals("52e"))
124            {
125               msg = "Invalid credentials";
126            }
127            else if (m.group(1).equals("530"))
128            {
129               msg = "Not permitted to logon at this time";
130            }
131            else if (m.group(1).equals("531"))
132            {
133               msg = "Not permitted to logon at this workstation";
134            }
135            else if (m.group(1).equals("532"))
136            {
137               msg = "Password expired";
138            }
139            else if (m.group(1).equals("533"))
140            {
141               msg = "Account disabled";
142            }
143            else if (m.group(1).equals("534"))
144            {
145               msg = "The user has not been granted the requested logon type at this machine";
146            }
147            else if (m.group(1).equals("701"))
148            {
149               msg = "Account expired";
150            }
151            else if (m.group(1).equals("773"))
152            {
153               msg = "User must reset password";
154            }
155            else if (m.group(1).equals("773"))
156            {
157               msg = "User account locked";
158            }
159         }
160      }
161
162      return msg;
163   }
164}