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}