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