blob: f33b8f29876a3f4fb1117dffc765864d0a88b641 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2002-2005 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package com.sun.jndi.ldap;
27
28import java.io.PrintStream;
29import java.io.OutputStream;
30import java.util.Hashtable;
31import java.util.StringTokenizer;
32
33import javax.naming.ldap.Control;
34import javax.naming.NamingException;
35import javax.naming.CommunicationException;
36import java.security.AccessController;
37import java.security.PrivilegedAction;
38
39import com.sun.jndi.ldap.pool.PoolCleaner;
40import 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
56public 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 (String) AccessController.doPrivileged(
389 new PrivilegedAction() {
390 public Object 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 = (Integer) AccessController.doPrivileged(
403 new PrivilegedAction() {
404 public Object 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 = (Long) AccessController.doPrivileged(
418 new PrivilegedAction() {
419 public Object 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}