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