001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.net.ldap; 020 021import java.io.IOException; 022import java.security.Security; 023import java.util.Enumeration; 024import java.util.Hashtable; 025 026import javax.naming.NamingEnumeration; 027import javax.naming.NamingException; 028import javax.naming.directory.Attribute; 029import javax.naming.directory.Attributes; 030import javax.naming.directory.BasicAttribute; 031import javax.naming.directory.BasicAttributes; 032import javax.naming.directory.DirContext; 033import javax.naming.directory.InitialDirContext; 034import javax.naming.directory.ModificationItem; 035import javax.naming.directory.SearchControls; 036import javax.naming.directory.SearchResult; 037import javax.naming.ldap.InitialLdapContext; 038 039import lucee.commons.lang.ClassException; 040import lucee.commons.lang.ClassUtil; 041import lucee.commons.lang.StringUtil; 042import lucee.runtime.exp.PageException; 043import lucee.runtime.op.Caster; 044import lucee.runtime.type.Collection; 045import lucee.runtime.type.KeyImpl; 046import lucee.runtime.type.Query; 047import lucee.runtime.type.QueryImpl; 048import lucee.runtime.type.util.ListUtil; 049 050import com.sun.net.ssl.internal.ssl.Provider; 051 052 053/** 054 * Ldap Client 055 */ 056public final class LDAPClient { 057 058 /** 059 * Field <code>SECURE_NONE</code> 060 */ 061 public static final short SECURE_NONE=0; 062 /** 063 * Field <code>SECURE_CFSSL_BASIC</code> 064 */ 065 public static final short SECURE_CFSSL_BASIC=1; 066 /** 067 * Field <code>SECURE_CFSSL_CLIENT_AUTH</code> 068 */ 069 public static final short SECURE_CFSSL_CLIENT_AUTH=2; 070 071 /** 072 * Field <code>SORT_TYPE_CASE</code> 073 */ 074 public static final int SORT_TYPE_CASE = 0; 075 /** 076 * Field <code>SORT_TYPE_NOCASE</code> 077 */ 078 public static final int SORT_TYPE_NOCASE = 1; 079 080 /** 081 * Field <code>SORT_DIRECTION_ASC</code> 082 */ 083 public static final int SORT_DIRECTION_ASC = 0; 084 /** 085 * Field <code>SORT_DIRECTION_DESC</code> 086 */ 087 public static final int SORT_DIRECTION_DESC = 1; 088 089 Hashtable env=new Hashtable(); 090 091 092 /** 093 * constructor of the class 094 * @param server 095 * @param port 096 * @param binaryColumns 097 */ 098 public LDAPClient(String server, int port,String[] binaryColumns) { 099 100 env.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory"); 101 env.put("java.naming.provider.url", "ldap://" + server+":"+port); 102 103 // rEAD AS bINARY 104 for(int i = 0; i < binaryColumns.length; i++)env.put("java.naming.ldap.attributes.binary", binaryColumns[i]); 105 106 // Referral 107 env.put("java.naming.referral", "ignore"); 108 } 109 110 /** 111 * sets username password for the connection 112 * @param username 113 * @param password 114 */ 115 public void setCredential(String username, String password) { 116 if(username != null) { 117 env.put("java.naming.security.principal", username); 118 env.put("java.naming.security.credentials", password); 119 } 120 else { 121 env.remove("java.naming.security.principal"); 122 env.remove("java.naming.security.credentials"); 123 } 124 } 125 126 /** 127 * sets the secure Level 128 * @param secureLevel [SECURE_CFSSL_BASIC, SECURE_CFSSL_CLIENT_AUTH, SECURE_NONE] 129 * @throws ClassNotFoundException 130 * @throws ClassException 131 */ 132 public void setSecureLevel(short secureLevel) throws ClassException { 133 // Security 134 if(secureLevel==SECURE_CFSSL_BASIC) { 135 env.put("java.naming.security.protocol", "ssl"); 136 env.put("java.naming.ldap.factory.socket", "javax.net.ssl.SSLSocketFactory"); 137 //Class.orName("com.sun.net.ssl.internal.ssl.Provider"); 138 ClassUtil.loadClass("com.sun.net.ssl.internal.ssl.Provider"); 139 140 Security.addProvider(new Provider()); 141 142 } 143 else if(secureLevel==SECURE_CFSSL_CLIENT_AUTH) { 144 env.put("java.naming.security.protocol", "ssl"); 145 env.put("java.naming.security.authentication", "EXTERNAL"); 146 } 147 else { 148 env.put("java.naming.security.authentication", "simple"); 149 env.remove("java.naming.security.protocol"); 150 env.remove("java.naming.ldap.factory.socket"); 151 } 152 } 153 154 /** 155 * sets thr referral 156 * @param referral 157 */ 158 public void setReferral(int referral) { 159 if(referral > 0) { 160 env.put("java.naming.referral", "follow"); 161 env.put("java.naming.ldap.referral.limit", Caster.toString(referral)); 162 } 163 else { 164 env.put("java.naming.referral", "ignore"); 165 env.remove("java.naming.ldap.referral.limit"); 166 } 167 } 168 169 170 171 /** 172 * adds LDAP entries to LDAP server 173 * @param dn 174 * @param attributes 175 * @param delimiter 176 * @throws NamingException 177 * @throws PageException 178 */ 179 public void add(String dn, String attributes, String delimiter, String seperator) throws NamingException, PageException { 180 DirContext ctx = new InitialDirContext(env); 181 ctx.createSubcontext(dn, toAttributes(attributes,delimiter,seperator)); 182 ctx.close(); 183 } 184 185 /** 186 * deletes LDAP entries on an LDAP server 187 * @param dn 188 * @throws NamingException 189 */ 190 public void delete(String dn) throws NamingException{ 191 DirContext ctx = new InitialDirContext(env); 192 ctx.destroySubcontext(dn); 193 ctx.close(); 194 } 195 196 /** 197 * modifies distinguished name attribute for LDAP entries on LDAP server 198 * @param dn 199 * @param attributes 200 * @throws NamingException 201 */ 202 public void modifydn(String dn, String attributes) throws NamingException{ 203 DirContext ctx = new InitialDirContext(env); 204 ctx.rename(dn, attributes); 205 ctx.close(); 206 } 207 208 public void modify(String dn, int modifytype, String strAttributes, String delimiter, String separator) throws NamingException, PageException { 209 210 DirContext context = new InitialDirContext(env); 211 String strArrAttributes[] = toStringAttributes(strAttributes,delimiter); 212 213 int count = 0; 214 for(int i=0; i<strArrAttributes.length; i++) { 215 String[] attributesValues = getAttributesValues(strArrAttributes[i], separator); 216 if(attributesValues == null)count++; 217 else count+=attributesValues.length; 218 } 219 220 ModificationItem modItems[] = new ModificationItem[count]; 221 BasicAttribute basicAttr = null; 222 int k = 0; 223 for(int i = 0; i < strArrAttributes.length; i++) { 224 String attribute = strArrAttributes[i]; 225 String type = getAttrValueType(attribute); 226 String values[] = getAttributesValues(attribute,separator); 227 228 if(modifytype==DirContext.REPLACE_ATTRIBUTE) { 229 if(values == null) basicAttr = new BasicAttribute(type); 230 else basicAttr = new BasicAttribute(type, values[0]); 231 232 modItems[k] = new ModificationItem(modifytype, basicAttr); 233 k++; 234 if(values != null && values.length > 1) { 235 for(int j = 1; j < values.length; j++) { 236 basicAttr = new BasicAttribute(type, values[j]); 237 modItems[k] = new ModificationItem(DirContext.ADD_ATTRIBUTE, basicAttr); 238 k++; 239 } 240 } 241 } 242 else { 243 for(int j = 0; j < values.length; j++) { 244 if(type != null || modifytype==DirContext.ADD_ATTRIBUTE) 245 basicAttr = new BasicAttribute(type, values[j]); 246 else basicAttr = new BasicAttribute(values[j]); 247 modItems[k] = new ModificationItem(modifytype, basicAttr); 248 k++; 249 } 250 } 251 } 252 253 context.modifyAttributes(dn, modItems); 254 context.close(); 255 256 } 257 258 259 260 /** 261 * @param dn 262 * @param strAttributes 263 * @param scope 264 * @param startrow 265 * @param maxrows 266 * @param timeout 267 * @param sort 268 * @param sortType 269 * @param sortDirection 270 * @param start 271 * @param separator 272 * @param filter 273 * @return 274 * @throws NamingException 275 * @throws PageException 276 * @throws IOException 277 */ 278 public Query query(String strAttributes,int scope, int startrow, int maxrows, int timeout, 279 String[] sort, int sortType, int sortDirection, 280 String start, String separator, String filter) 281 throws NamingException, PageException, IOException { 282 //strAttributes=strAttributes.trim(); 283 boolean attEQAsterix=strAttributes.trim().equals("*"); 284 String[] attributes = attEQAsterix?new String[]{"name","value"}:toStringAttributes(strAttributes,","); 285 286 287 288 // Control 289 SearchControls controls = new SearchControls(); 290 controls.setReturningObjFlag(true); 291 controls.setSearchScope(scope); 292 if(!attEQAsterix)controls.setReturningAttributes(toStringAttributes(strAttributes,",")); 293 if(maxrows>0)controls.setCountLimit(startrow + maxrows + 1); 294 if(timeout>0)controls.setTimeLimit(timeout); 295 296 297 InitialLdapContext context = new InitialLdapContext(env, null); 298 299 // Search 300 Query qry=new QueryImpl(attributes,0,"query"); 301 try { 302 NamingEnumeration results = context.search(start, filter, controls); 303 304 // Fill result 305 int row=1; 306 if(!attEQAsterix) { 307 while(results.hasMoreElements()) { 308 SearchResult resultRow = (SearchResult)results.next(); 309 if(row++<startrow)continue; 310 311 int len=qry.addRow(); 312 NamingEnumeration rowEnum = resultRow.getAttributes().getAll(); 313 String dn = resultRow.getNameInNamespace(); 314 qry.setAtEL("dn",len,dn); 315 while(rowEnum.hasMore()) { 316 Attribute attr = (Attribute)rowEnum.next(); 317 Collection.Key key = KeyImpl.init(attr.getID()); 318 Enumeration values = attr.getAll(); 319 Object value; 320 String existing,strValue; 321 while(values.hasMoreElements()) { 322 value = values.nextElement(); 323 strValue=Caster.toString(value,null); 324 existing=Caster.toString(qry.getAt(key, len,null),null); 325 if(!StringUtil.isEmpty(existing) && !StringUtil.isEmpty(strValue)) { 326 value = existing + separator + strValue; 327 } 328 else if(!StringUtil.isEmpty(existing)) 329 value = existing; 330 331 qry.setAtEL(key,len,value); 332 } 333 } 334 if(maxrows>0 && len>=maxrows)break; 335 } 336 } 337 else { 338 339 outer:while(results.hasMoreElements()) { 340 SearchResult resultRow = (SearchResult)results.next(); 341 if(row++<startrow)continue; 342 343 Attributes attributesRow = resultRow.getAttributes(); 344 NamingEnumeration rowEnum = attributesRow.getIDs(); 345 while(rowEnum.hasMoreElements()) { 346 int len=qry.addRow(); 347 String name = Caster.toString(rowEnum.next()); 348 Object value=null; 349 350 try { 351 value=attributesRow.get(name).get(); 352 }catch(Exception e) {} 353 354 qry.setAtEL("name",len,name); 355 qry.setAtEL("value",len,value); 356 if(maxrows>0 && len>=maxrows)break outer; 357 } 358 qry.setAtEL("name",qry.size(),"dn"); 359 } 360 } 361 } 362 finally { 363 context.close(); 364 } 365 // Sort 366 if(sort!=null && sort.length>0) { 367 int order = sortDirection==SORT_DIRECTION_ASC ? Query.ORDER_ASC : Query.ORDER_DESC; 368 for(int i=sort.length-1;i>=0;i--) { 369 String item=sort[i]; 370 if(item.indexOf(' ')!=-1)item=ListUtil.first(item," ",true); 371 qry.sort(KeyImpl.getInstance(item),order); 372 //keys[i] = new SortKey(item); 373 } 374 } 375 return qry; 376 } 377 378 private static String[] toStringAttributes(String strAttributes,String delimiter) throws PageException { 379 return ListUtil.toStringArrayTrim(ListUtil.listToArrayRemoveEmpty(strAttributes,delimiter)); 380 } 381 382 private static Attributes toAttributes(String strAttributes,String delimiter, String separator) throws PageException { 383 String[] arrAttr = toStringAttributes(strAttributes,delimiter); 384 385 386 BasicAttributes attributes = new BasicAttributes(); 387 for(int i=0; i<arrAttr.length; i++) { 388 String strAttr = arrAttr[i]; 389 390 // Type 391 int eqIndex=strAttr.indexOf('='); 392 Attribute attr = new BasicAttribute((eqIndex != -1)?strAttr.substring(0, eqIndex).trim():null); 393 394 // Value 395 String strValue = (eqIndex!=-1)?strAttr.substring( eqIndex+ 1):strAttr; 396 String[] arrValue=ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(strValue,separator)); 397 398 // Fill 399 for(int y=0; y<arrValue.length; y++) { 400 attr.add(arrValue[y]); 401 } 402 attributes.put(attr); 403 } 404 return attributes; 405 406 } 407 408 private String getAttrValueType(String attribute) { 409 int eqIndex=attribute.indexOf("="); 410 if(eqIndex != -1) return attribute.substring(0, eqIndex).trim(); 411 return null; 412 } 413 414 private String[] getAttributesValues(String attribute, String separator) throws PageException { 415 String strValue = attribute.substring(attribute.indexOf("=") + 1); 416 if(strValue.length() == 0) return null; 417 return ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(strValue,separator.equals(", ") ? "," : separator)); 418 } 419 420}