1 /* 2 * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.jndi.ldap; 27 28 import java.io.PrintStream; 29 import java.io.OutputStream; 30 import java.util.Hashtable; 31 import java.util.Locale; 32 import java.util.StringTokenizer; 33 34 import javax.naming.ldap.Control; 35 import javax.naming.NamingException; 36 import javax.naming.CommunicationException; 37 import java.security.AccessController; 38 import java.security.PrivilegedAction; 39 40 import com.sun.jndi.ldap.pool.PoolCleaner; 41 import com.sun.jndi.ldap.pool.Pool; 42 43 /** 44 * Contains utilities for managing connection pools of LdapClient. 45 * Contains method for 46 * - checking whether attempted connection creation may be pooled 47 * - creating a pooled connection 48 * - closing idle connections. 49 * 50 * If a timeout period has been configured, then it will automatically 51 * close and remove idle connections (those that have not been 52 * used for the duration of the timeout period). 53 * 54 * @author Rosanna Lee 55 */ 56 57 public final class LdapPoolManager { 58 private static final String DEBUG = 59 "com.sun.jndi.ldap.connect.pool.debug"; 60 61 public static final boolean debug = 62 "all".equalsIgnoreCase(getProperty(DEBUG, null)); 63 64 public static final boolean trace = debug || 65 "fine".equalsIgnoreCase(getProperty(DEBUG, null)); 66 67 // ---------- System properties for connection pooling 68 69 // Authentication mechanisms of connections that may be pooled 70 private static final String POOL_AUTH = 71 "com.sun.jndi.ldap.connect.pool.authentication"; 72 73 // Protocol types of connections that may be pooled 74 private static final String POOL_PROTOCOL = 75 "com.sun.jndi.ldap.connect.pool.protocol"; 76 77 // Maximum number of identical connections per pool 78 private static final String MAX_POOL_SIZE = 79 "com.sun.jndi.ldap.connect.pool.maxsize"; 80 81 // Preferred number of identical connections per pool 82 private static final String PREF_POOL_SIZE = 83 "com.sun.jndi.ldap.connect.pool.prefsize"; 84 85 // Initial number of identical connections per pool 86 private static final String INIT_POOL_SIZE = 87 "com.sun.jndi.ldap.connect.pool.initsize"; 88 89 // Milliseconds to wait before closing idle connections 90 private static final String POOL_TIMEOUT = 91 "com.sun.jndi.ldap.connect.pool.timeout"; 92 93 // Properties for DIGEST 94 private static final String SASL_CALLBACK = 95 "java.naming.security.sasl.callback"; 96 97 // --------- Constants 98 private static final int DEFAULT_MAX_POOL_SIZE = 0; 99 private static final int DEFAULT_PREF_POOL_SIZE = 0; 100 private static final int DEFAULT_INIT_POOL_SIZE = 1; 101 private static final int DEFAULT_TIMEOUT = 0; // no timeout 102 private static final String DEFAULT_AUTH_MECHS = "none simple"; 103 private static final String DEFAULT_PROTOCOLS = "plain"; 104 105 private static final int NONE = 0; // indices into pools 106 private static final int SIMPLE = 1; 107 private static final int DIGEST = 2; 108 109 // --------- static fields 110 private static final long idleTimeout;// ms to wait before closing idle conn 111 private static final int maxSize; // max num of identical conns/pool 112 private static final int prefSize; // preferred num of identical conns/pool 113 private static final int initSize; // initial num of identical conns/pool 114 115 private static boolean supportPlainProtocol = false; 116 private static boolean supportSslProtocol = false; 117 118 // List of pools used for different auth types 119 private static final Pool[] pools = new Pool[3]; 120 121 static { 122 maxSize = getInteger(MAX_POOL_SIZE, DEFAULT_MAX_POOL_SIZE); 123 124 prefSize = getInteger(PREF_POOL_SIZE, DEFAULT_PREF_POOL_SIZE); 125 126 initSize = getInteger(INIT_POOL_SIZE, DEFAULT_INIT_POOL_SIZE); 127 128 idleTimeout = getLong(POOL_TIMEOUT, DEFAULT_TIMEOUT); 129 130 // Determine supported authentication mechanisms 131 String str = getProperty(POOL_AUTH, DEFAULT_AUTH_MECHS); 132 StringTokenizer parser = new StringTokenizer(str); 133 int count = parser.countTokens(); 134 String mech; 135 int p; 136 for (int i = 0; i < count; i++) { 137 mech = parser.nextToken().toLowerCase(Locale.ENGLISH); 138 if (mech.equals("anonymous")) { 139 mech = "none"; 140 } 141 142 p = findPool(mech); 143 if (p >= 0 && pools[p] == null) { 144 pools[p] = new Pool(initSize, prefSize, maxSize); 145 } 146 } 147 148 // Determine supported protocols 149 str= getProperty(POOL_PROTOCOL, DEFAULT_PROTOCOLS); 150 parser = new StringTokenizer(str); 151 count = parser.countTokens(); 152 String proto; 153 for (int i = 0; i < count; i++) { 154 proto = parser.nextToken(); 155 if ("plain".equalsIgnoreCase(proto)) { 156 supportPlainProtocol = true; 157 } else if ("ssl".equalsIgnoreCase(proto)) { 158 supportSslProtocol = true; 159 } else { 160 // ignore 161 } 162 } 163 164 if (idleTimeout > 0) { 165 // Create cleaner to expire idle connections 166 new PoolCleaner(idleTimeout, pools).start(); 167 } 168 169 if (debug) { 170 showStats(System.err); 171 } 172 } 173 174 // Cannot instantiate one of these 175 private LdapPoolManager() { 176 } 177 178 /** 179 * Find the index of the pool for the specified mechanism. If not 180 * one of "none", "simple", "DIGEST-MD5", or "GSSAPI", 181 * return -1. 182 * @param mech mechanism type 183 */ 184 private static int findPool(String mech) { 185 if ("none".equalsIgnoreCase(mech)) { 186 return NONE; 187 } else if ("simple".equalsIgnoreCase(mech)) { 188 return SIMPLE; 189 } else if ("digest-md5".equalsIgnoreCase(mech)) { 190 return DIGEST; 191 } 192 return -1; 193 } 194 195 /** 196 * Determines whether pooling is allowed given information on how 197 * the connection will be used. 198 * 199 * Non-configurable rejections: 200 * - nonstandard socketFactory has been specified: the pool manager 201 * cannot track input or parameters used by the socket factory and 202 * thus has no way of determining whether two connection requests 203 * are equivalent. Maybe in the future it might add a list of allowed 204 * socket factories to be configured 205 * - trace enabled (except when debugging) 206 * - for Digest authentication, if a callback handler has been specified: 207 * the pool manager cannot track input collected by the handler 208 * and thus has no way of determining whether two connection requests are 209 * equivalent. Maybe in the future it might add a list of allowed 210 * callback handlers. 211 * 212 * Configurable tests: 213 * - Pooling for the requested protocol (plain or ssl) is supported 214 * - Pooling for the requested authentication mechanism is supported 215 * 216 */ 217 static boolean isPoolingAllowed(String socketFactory, OutputStream trace, 218 String authMech, String protocol, Hashtable<?,?> env) 219 throws NamingException { 220 221 if (trace != null && !debug 222 223 // Requesting plain protocol but it is not supported 224 || (protocol == null && !supportPlainProtocol) 225 226 // Requesting ssl protocol but it is not supported 227 || ("ssl".equalsIgnoreCase(protocol) && !supportSslProtocol)) { 228 229 d("Pooling disallowed due to tracing or unsupported pooling of protocol"); 230 return false; 231 } 232 // pooling of custom socket factory is possible only if the 233 // socket factory interface implements java.util.comparator 234 String COMPARATOR = "java.util.Comparator"; 235 boolean foundSockCmp = false; 236 if ((socketFactory != null) && 237 !socketFactory.equals(LdapCtx.DEFAULT_SSL_FACTORY)) { 238 try { 239 Class<?> socketFactoryClass = Obj.helper.loadClass(socketFactory); 240 Class<?>[] interfaces = socketFactoryClass.getInterfaces(); 241 for (int i = 0; i < interfaces.length; i++) { 242 if (interfaces[i].getCanonicalName().equals(COMPARATOR)) { 243 foundSockCmp = true; 244 } 245 } 246 } catch (Exception e) { 247 CommunicationException ce = 248 new CommunicationException("Loading the socket factory"); 249 ce.setRootCause(e); 250 throw ce; 251 } 252 if (!foundSockCmp) { 253 return false; 254 } 255 } 256 // Cannot use pooling if authMech is not a supported mechs 257 // Cannot use pooling if authMech contains multiple mechs 258 int p = findPool(authMech); 259 if (p < 0 || pools[p] == null) { 260 d("authmech not found: ", authMech); 261 262 return false; 263 } 264 265 d("using authmech: ", authMech); 266 267 switch (p) { 268 case NONE: 269 case SIMPLE: 270 return true; 271 272 case DIGEST: 273 // Provider won't be able to determine connection identity 274 // if an alternate callback handler is used 275 return (env == null || env.get(SASL_CALLBACK) == null); 276 } 277 return false; 278 } 279 280 /** 281 * Obtains a pooled connection that either already exists or is 282 * newly created using the parameters supplied. If it is newly 283 * created, it needs to go through the authentication checks to 284 * determine whether an LDAP bind is necessary. 285 * 286 * Caller needs to invoke ldapClient.authenticateCalled() to 287 * determine whether ldapClient.authenticate() needs to be invoked. 288 * Caller has that responsibility because caller needs to deal 289 * with the LDAP bind response, which might involve referrals, 290 * response controls, errors, etc. This method is responsible only 291 * for establishing the connection. 292 * 293 * @return an LdapClient that is pooled. 294 */ 295 static LdapClient getLdapClient(String host, int port, String socketFactory, 296 int connTimeout, int readTimeout, OutputStream trace, int version, 297 String authMech, Control[] ctls, String protocol, String user, 298 Object passwd, Hashtable<?,?> env) throws NamingException { 299 300 // Create base identity for LdapClient 301 ClientId id = null; 302 Pool pool; 303 304 int p = findPool(authMech); 305 if (p < 0 || (pool=pools[p]) == null) { 306 throw new IllegalArgumentException( 307 "Attempting to use pooling for an unsupported mechanism: " + 308 authMech); 309 } 310 switch (p) { 311 case NONE: 312 id = new ClientId(version, host, port, protocol, 313 ctls, trace, socketFactory); 314 break; 315 316 case SIMPLE: 317 // Add identity information used in simple authentication 318 id = new SimpleClientId(version, host, port, protocol, 319 ctls, trace, socketFactory, user, passwd); 320 break; 321 322 case DIGEST: 323 // Add user/passwd/realm/authzid/qop/strength/maxbuf/mutual/policy* 324 id = new DigestClientId(version, host, port, protocol, 325 ctls, trace, socketFactory, user, passwd, env); 326 break; 327 } 328 329 return (LdapClient) pool.getPooledConnection(id, connTimeout, 330 new LdapClientFactory(host, port, socketFactory, connTimeout, 331 readTimeout, trace)); 332 } 333 334 public static void showStats(PrintStream out) { 335 out.println("***** start *****"); 336 out.println("idle timeout: " + idleTimeout); 337 out.println("maximum pool size: " + maxSize); 338 out.println("preferred pool size: " + prefSize); 339 out.println("initial pool size: " + initSize); 340 out.println("protocol types: " + (supportPlainProtocol ? "plain " : "") + 341 (supportSslProtocol ? "ssl" : "")); 342 out.println("authentication types: " + 343 (pools[NONE] != null ? "none " : "") + 344 (pools[SIMPLE] != null ? "simple " : "") + 345 (pools[DIGEST] != null ? "DIGEST-MD5 " : "")); 346 347 for (int i = 0; i < pools.length; i++) { 348 if (pools[i] != null) { 349 out.println( 350 (i == NONE ? "anonymous pools" : 351 i == SIMPLE ? "simple auth pools" : 352 i == DIGEST ? "digest pools" : "") 353 + ":"); 354 pools[i].showStats(out); 355 } 356 } 357 out.println("***** end *****"); 358 } 359 360 /** 361 * Closes idle connections idle since specified time. 362 * 363 * @param threshold Close connections idle since this time, as 364 * specified in milliseconds since "the epoch". 365 * @see java.util.Date 366 */ 367 public static void expire(long threshold) { 368 for (int i = 0; i < pools.length; i++) { 369 if (pools[i] != null) { 370 pools[i].expire(threshold); 371 } 372 } 373 } 374 375 private static void d(String msg) { 376 if (debug) { 377 System.err.println("LdapPoolManager: " + msg); 378 } 379 } 380 381 private static void d(String msg, String o) { 382 if (debug) { 383 System.err.println("LdapPoolManager: " + msg + o); 384 } 385 } 386 387 private static final String getProperty(final String propName, 388 final String defVal) { 389 return AccessController.doPrivileged( 390 new PrivilegedAction<String>() { 391 public String run() { 392 try { 393 return System.getProperty(propName, defVal); 394 } catch (SecurityException e) { 395 return defVal; 396 } 397 } 398 }); 399 } 400 401 private static final int getInteger(final String propName, 402 final int defVal) { 403 Integer val = AccessController.doPrivileged( 404 new PrivilegedAction<Integer>() { 405 public Integer run() { 406 try { 407 return Integer.getInteger(propName, defVal); 408 } catch (SecurityException e) { 409 return new Integer(defVal); 410 } 411 } 412 }); 413 return val.intValue(); 414 } 415 416 private static final long getLong(final String propName, 417 final long defVal) { 418 Long val = AccessController.doPrivileged( 419 new PrivilegedAction<Long>() { 420 public Long run() { 421 try { 422 return Long.getLong(propName, defVal); 423 } catch (SecurityException e) { 424 return new Long(defVal); 425 } 426 } 427 }); 428 return val.longValue(); 429 } 430 }