Mercurial > hg > openjdk > jigsaw > jdk
changeset 6342:c0736b62160e
8000487: Java JNDI connection library on ldap conn is not honoring configured timeout
Reviewed-by: vinnie
author | robm |
---|---|
date | Mon, 15 Oct 2012 22:34:35 +0100 |
parents | 7055257a25c4 |
children | 32452042b781 |
files | src/share/classes/com/sun/jndi/ldap/Connection.java src/share/classes/com/sun/jndi/ldap/LdapClient.java test/com/sun/jndi/ldap/LdapTimeoutTest.java test/com/sun/jndi/ldap/LdapsReadTimeoutTest.java test/com/sun/jndi/ldap/ReadTimeoutTest.java |
diffstat | 5 files changed, 309 insertions(+), 355 deletions(-) [+] |
line wrap: on
line diff
--- a/src/share/classes/com/sun/jndi/ldap/Connection.java Mon Oct 15 03:26:11 2012 +0100 +++ b/src/share/classes/com/sun/jndi/ldap/Connection.java Mon Oct 15 22:34:35 2012 +0100 @@ -157,7 +157,8 @@ volatile IOException closureReason = null; volatile boolean useable = true; // is Connection still useable - private int readTimeout; + int readTimeout; + int connectTimeout; // true means v3; false means v2 // Called in LdapClient.authenticate() (which is synchronized) @@ -187,6 +188,7 @@ this.port = port; this.parent = parent; this.readTimeout = readTimeout; + this.connectTimeout = connectTimeout; if (trace != null) { traceFile = trace;
--- a/src/share/classes/com/sun/jndi/ldap/LdapClient.java Mon Oct 15 03:26:11 2012 +0100 +++ b/src/share/classes/com/sun/jndi/ldap/LdapClient.java Mon Oct 15 22:34:35 2012 +0100 @@ -150,149 +150,155 @@ String authMechanism, Control[] ctls, Hashtable<?,?> env) throws NamingException { - authenticateCalled = true; + int readTimeout = conn.readTimeout; + conn.readTimeout = conn.connectTimeout; + LdapResult res = null; try { - ensureOpen(); - } catch (IOException e) { - NamingException ne = new CommunicationException(); - ne.setRootCause(e); - throw ne; - } + authenticateCalled = true; + + try { + ensureOpen(); + } catch (IOException e) { + NamingException ne = new CommunicationException(); + ne.setRootCause(e); + throw ne; + } + + switch (version) { + case LDAP_VERSION3_VERSION2: + case LDAP_VERSION3: + isLdapv3 = true; + break; + case LDAP_VERSION2: + isLdapv3 = false; + break; + default: + throw new CommunicationException("Protocol version " + version + + " not supported"); + } + + if (authMechanism.equalsIgnoreCase("none") || + authMechanism.equalsIgnoreCase("anonymous")) { - switch (version) { - case LDAP_VERSION3_VERSION2: - case LDAP_VERSION3: - isLdapv3 = true; - break; - case LDAP_VERSION2: - isLdapv3 = false; - break; - default: - throw new CommunicationException("Protocol version " + version + - " not supported"); - } - - LdapResult res = null; - - if (authMechanism.equalsIgnoreCase("none") || - authMechanism.equalsIgnoreCase("anonymous")) { - - // Perform LDAP bind if we are reauthenticating, using LDAPv2, - // supporting failover to LDAPv2, or controls have been supplied. - if (!initial || - (version == LDAP_VERSION2) || - (version == LDAP_VERSION3_VERSION2) || - ((ctls != null) && (ctls.length > 0))) { + // Perform LDAP bind if we are reauthenticating, using LDAPv2, + // supporting failover to LDAPv2, or controls have been supplied. + if (!initial || + (version == LDAP_VERSION2) || + (version == LDAP_VERSION3_VERSION2) || + ((ctls != null) && (ctls.length > 0))) { + try { + // anonymous bind; update name/pw for LDAPv2 retry + res = ldapBind(name=null, (byte[])(pw=null), ctls, null, + false); + if (res.status == LdapClient.LDAP_SUCCESS) { + conn.setBound(); + } + } catch (IOException e) { + NamingException ne = + new CommunicationException("anonymous bind failed: " + + conn.host + ":" + conn.port); + ne.setRootCause(e); + throw ne; + } + } else { + // Skip LDAP bind for LDAPv3 anonymous bind + res = new LdapResult(); + res.status = LdapClient.LDAP_SUCCESS; + } + } else if (authMechanism.equalsIgnoreCase("simple")) { + // simple authentication + byte[] encodedPw = null; try { - // anonymous bind; update name/pw for LDAPv2 retry - res = ldapBind(name=null, (byte[])(pw=null), ctls, null, - false); + encodedPw = encodePassword(pw, isLdapv3); + res = ldapBind(name, encodedPw, ctls, null, false); if (res.status == LdapClient.LDAP_SUCCESS) { conn.setBound(); } } catch (IOException e) { NamingException ne = - new CommunicationException("anonymous bind failed: " + + new CommunicationException("simple bind failed: " + + conn.host + ":" + conn.port); + ne.setRootCause(e); + throw ne; + } finally { + // If pw was copied to a new array, clear that array as + // a security precaution. + if (encodedPw != pw && encodedPw != null) { + for (int i = 0; i < encodedPw.length; i++) { + encodedPw[i] = 0; + } + } + } + } else if (isLdapv3) { + // SASL authentication + try { + res = LdapSasl.saslBind(this, conn, conn.host, name, pw, + authMechanism, env, ctls); + if (res.status == LdapClient.LDAP_SUCCESS) { + conn.setBound(); + } + } catch (IOException e) { + NamingException ne = + new CommunicationException("SASL bind failed: " + conn.host + ":" + conn.port); ne.setRootCause(e); throw ne; } } else { - // Skip LDAP bind for LDAPv3 anonymous bind - res = new LdapResult(); - res.status = LdapClient.LDAP_SUCCESS; + throw new AuthenticationNotSupportedException(authMechanism); } - } else if (authMechanism.equalsIgnoreCase("simple")) { - // simple authentication - byte[] encodedPw = null; - try { - encodedPw = encodePassword(pw, isLdapv3); - res = ldapBind(name, encodedPw, ctls, null, false); - if (res.status == LdapClient.LDAP_SUCCESS) { - conn.setBound(); - } - } catch (IOException e) { - NamingException ne = - new CommunicationException("simple bind failed: " + - conn.host + ":" + conn.port); - ne.setRootCause(e); - throw ne; - } finally { - // If pw was copied to a new array, clear that array as - // a security precaution. - if (encodedPw != pw && encodedPw != null) { - for (int i = 0; i < encodedPw.length; i++) { - encodedPw[i] = 0; + + // + // re-try login using v2 if failing over + // + if (initial && + (res.status == LdapClient.LDAP_PROTOCOL_ERROR) && + (version == LdapClient.LDAP_VERSION3_VERSION2) && + (authMechanism.equalsIgnoreCase("none") || + authMechanism.equalsIgnoreCase("anonymous") || + authMechanism.equalsIgnoreCase("simple"))) { + + byte[] encodedPw = null; + try { + isLdapv3 = false; + encodedPw = encodePassword(pw, false); + res = ldapBind(name, encodedPw, ctls, null, false); + if (res.status == LdapClient.LDAP_SUCCESS) { + conn.setBound(); + } + } catch (IOException e) { + NamingException ne = + new CommunicationException(authMechanism + ":" + + conn.host + ":" + conn.port); + ne.setRootCause(e); + throw ne; + } finally { + // If pw was copied to a new array, clear that array as + // a security precaution. + if (encodedPw != pw && encodedPw != null) { + for (int i = 0; i < encodedPw.length; i++) { + encodedPw[i] = 0; + } } } } - } else if (isLdapv3) { - // SASL authentication - try { - res = LdapSasl.saslBind(this, conn, conn.host, name, pw, - authMechanism, env, ctls); - if (res.status == LdapClient.LDAP_SUCCESS) { - conn.setBound(); - } - } catch (IOException e) { - NamingException ne = - new CommunicationException("SASL bind failed: " + - conn.host + ":" + conn.port); - ne.setRootCause(e); - throw ne; + + // principal name not found + // (map NameNotFoundException to AuthenticationException) + // %%% This is a workaround for Netscape servers returning + // %%% no such object when the principal name is not found + // %%% Note that when this workaround is applied, it does not allow + // %%% response controls to be recorded by the calling context + if (res.status == LdapClient.LDAP_NO_SUCH_OBJECT) { + throw new AuthenticationException( + getErrorMessage(res.status, res.errorMessage)); } - } else { - throw new AuthenticationNotSupportedException(authMechanism); + conn.setV3(isLdapv3); + return res; + } finally { + conn.readTimeout = readTimeout; } - - // - // re-try login using v2 if failing over - // - if (initial && - (res.status == LdapClient.LDAP_PROTOCOL_ERROR) && - (version == LdapClient.LDAP_VERSION3_VERSION2) && - (authMechanism.equalsIgnoreCase("none") || - authMechanism.equalsIgnoreCase("anonymous") || - authMechanism.equalsIgnoreCase("simple"))) { - - byte[] encodedPw = null; - try { - isLdapv3 = false; - encodedPw = encodePassword(pw, false); - res = ldapBind(name, encodedPw, ctls, null, false); - if (res.status == LdapClient.LDAP_SUCCESS) { - conn.setBound(); - } - } catch (IOException e) { - NamingException ne = - new CommunicationException(authMechanism + ":" + - conn.host + ":" + conn.port); - ne.setRootCause(e); - throw ne; - } finally { - // If pw was copied to a new array, clear that array as - // a security precaution. - if (encodedPw != pw && encodedPw != null) { - for (int i = 0; i < encodedPw.length; i++) { - encodedPw[i] = 0; - } - } - } - } - - // principal name not found - // (map NameNotFoundException to AuthenticationException) - // %%% This is a workaround for Netscape servers returning - // %%% no such object when the principal name is not found - // %%% Note that when this workaround is applied, it does not allow - // %%% response controls to be recorded by the calling context - if (res.status == LdapClient.LDAP_NO_SUCH_OBJECT) { - throw new AuthenticationException( - getErrorMessage(res.status, res.errorMessage)); - } - conn.setV3(isLdapv3); - return res; } /**
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/com/sun/jndi/ldap/LdapTimeoutTest.java Mon Oct 15 22:34:35 2012 +0100 @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 7094377 8000487 6176036 7056489 + * @summary Timeout tests for ldap + */ + +import java.net.Socket; +import java.net.ServerSocket; +import java.net.SocketTimeoutException; +import java.io.*; +import javax.naming.*; +import javax.naming.directory.*; +import java.util.Hashtable; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class LdapTimeoutTest { + private static final ScheduledExecutorService pool = + Executors.newScheduledThreadPool(1); + static volatile int passed = 0, failed = 0; + static void pass() {passed++;} + static void fail() {failed++; Thread.dumpStack();} + + public static void main(String[] args) throws Exception { + ServerSocket serverSock = new ServerSocket(0); + Server s = new Server(serverSock); + s.start(); + Thread.sleep(200); + + Hashtable env = new Hashtable(11); + env.put(Context.INITIAL_CONTEXT_FACTORY, + "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, "ldap://localhost:" + + serverSock.getLocalPort()); + + env.put(Context.SECURITY_AUTHENTICATION,"simple"); + + env.put(Context.SECURITY_PRINCIPAL, "user"); + env.put(Context.SECURITY_CREDENTIALS, "password"); + + env.put("com.sun.jndi.ldap.connect.timeout", "10"); + env.put("com.sun.jndi.ldap.read.timeout", "3000"); + + InitialContext ctx = null; + try { + new LdapTimeoutTest().ldapReadTimeoutTest(env, false); + new LdapTimeoutTest().ldapReadTimeoutTest(env, true); + new LdapTimeoutTest().simpleAuthConnectTest(env); + } finally { + s.interrupt(); + LdapTimeoutTest.pool.shutdown(); + } + + System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); + if (failed > 0) throw new AssertionError("Some tests failed"); + } + + void ldapReadTimeoutTest(Hashtable env, boolean ssl) { + InitialContext ctx = null; + if (ssl) env.put(Context.SECURITY_PROTOCOL, "ssl"); + ScheduledFuture killer = killSwitch(); + long start = System.nanoTime(); + try { + ctx = new InitialDirContext(env); + SearchControls scl = new SearchControls(); + scl.setSearchScope(SearchControls.SUBTREE_SCOPE); + NamingEnumeration<SearchResult> answer = ((InitialDirContext)ctx) + .search("ou=People,o=JNDITutorial", "(objectClass=*)", scl); + // shouldn't reach here + fail(); + } catch (NamingException e) { + if (ssl) { + if (e.getCause() instanceof SocketTimeoutException) { + pass(); + } else if (e.getCause() instanceof InterruptedIOException) { + Thread.interrupted(); + fail(); + } + } else { + pass(); + } + } finally { + if (!shutItDown(killer, ctx)) fail(); + } + } + + void simpleAuthConnectTest(Hashtable env) { + InitialContext ctx = null; + ScheduledFuture killer = killSwitch(); + long start = System.nanoTime(); + try { + ctx = new InitialDirContext(env); + // shouldn't reach here + System.err.println("Fail: InitialDirContext succeeded"); + fail(); + } catch (NamingException e) { + long end = System.nanoTime(); + if (e.getCause() instanceof SocketTimeoutException) { + if (TimeUnit.NANOSECONDS.toMillis(end - start) < 2900) { + pass(); + } else { + System.err.println("Fail: Waited too long"); + fail(); + } + } else if (e.getCause() instanceof InterruptedIOException) { + Thread.interrupted(); + fail(); + } else { + fail(); + } + } finally { + if (!shutItDown(killer, ctx)) fail(); + } + } + + boolean shutItDown(ScheduledFuture killer, InitialContext ctx) { + killer.cancel(true); + try { + if (ctx != null) ctx.close(); + return true; + } catch (NamingException ex) { + return false; + } + } + + ScheduledFuture killSwitch() { + final Thread current = Thread.currentThread(); + return LdapTimeoutTest.pool.schedule(new Callable<Void>() { + public Void call() throws Exception { + System.err.println("Fail: killSwitch()"); + current.interrupt(); + return null; + } + }, 5000, TimeUnit.MILLISECONDS); + } + + static class Server extends Thread { + final ServerSocket serverSock; + + Server(ServerSocket serverSock) { + this.serverSock = serverSock; + } + + public void run() { + try { + Socket socket = serverSock.accept(); + } catch (IOException e) {} + } + } +} +
--- a/test/com/sun/jndi/ldap/LdapsReadTimeoutTest.java Mon Oct 15 03:26:11 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/** - * @test - * @bug 7094377 - * @summary Com.sun.jndi.ldap.read.timeout doesn't work with ldaps. - */ - -import java.net.Socket; -import java.net.ServerSocket; -import java.io.*; -import javax.naming.*; -import javax.naming.directory.*; -import java.util.Hashtable; - -public class LdapsReadTimeoutTest { - - public static void main(String[] args) throws Exception { - boolean passed = false; - - // create the server - try (Server server = Server.create()) { - // Set up the environment for creating the initial context - Hashtable<String,Object> env = new Hashtable<>(11); - env.put(Context.INITIAL_CONTEXT_FACTORY, - "com.sun.jndi.ldap.LdapCtxFactory"); - env.put("com.sun.jndi.ldap.connect.timeout", "1000"); - env.put("com.sun.jndi.ldap.read.timeout", "1000"); - env.put(Context.PROVIDER_URL, "ldaps://localhost:" + server.port()); - - - // Create initial context - DirContext ctx = new InitialDirContext(env); - try { - System.out.println("LDAP Client: Connected to the Server"); - - SearchControls scl = new SearchControls(); - scl.setSearchScope(SearchControls.SUBTREE_SCOPE); - System.out.println("Performing Search"); - NamingEnumeration<SearchResult> answer = - ctx.search("ou=People,o=JNDITutorial", "(objectClass=*)", scl); - } finally { - // Close the context when we're done - ctx.close(); - } - } catch (NamingException e) { - passed = true; - e.printStackTrace(); - } - - if (!passed) { - throw new Exception("Read timeout test failed," + - " read timeout exception not thrown"); - } - System.out.println("The test PASSED"); - } - - static class Server implements Runnable, Closeable { - private final ServerSocket ss; - private Socket sref; - - private Server(ServerSocket ss) { - this.ss = ss; - } - - static Server create() throws IOException { - Server server = new Server(new ServerSocket(0)); - new Thread(server).start(); - return server; - } - - int port() { - return ss.getLocalPort(); - } - - public void run() { - try (Socket s = ss.accept()) { - sref = s; - System.out.println("Server: Connection accepted"); - BufferedInputStream bis = - new BufferedInputStream(s.getInputStream()); - byte[] buf = new byte[100]; - int n; - do { - n = bis.read(buf); - } while (n > 0); - } catch (IOException e) { - // ignore - } - } - - public void close() throws IOException { - ss.close(); - sref.close(); - } - } -}
--- a/test/com/sun/jndi/ldap/ReadTimeoutTest.java Mon Oct 15 03:26:11 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/** - * @test - * @bug 6176036 7056489 - * @summary Read-timeout specification for LDAP operations - */ - -import java.net.Socket; -import java.net.ServerSocket; -import java.io.*; -import javax.naming.*; -import javax.naming.directory.*; -import java.util.Hashtable; - -public class ReadTimeoutTest { - - public static void main(String[] args) throws Exception { - boolean passed = false; - - // create the server - try (Server server = Server.create()) { - // Set up the environment for creating the initial context - Hashtable<String,Object> env = new Hashtable<>(11); - env.put(Context.INITIAL_CONTEXT_FACTORY, - "com.sun.jndi.ldap.LdapCtxFactory"); - env.put("com.sun.jndi.ldap.read.timeout", "1000"); - env.put(Context.PROVIDER_URL, "ldap://localhost:" + server.port()); - - - // Create initial context - DirContext ctx = new InitialDirContext(env); - try { - System.out.println("LDAP Client: Connected to the Server"); - - SearchControls scl = new SearchControls(); - scl.setSearchScope(SearchControls.SUBTREE_SCOPE); - System.out.println("Performing Search"); - NamingEnumeration<SearchResult> answer = - ctx.search("ou=People,o=JNDITutorial", "(objectClass=*)", scl); - } finally { - // Close the context when we're done - ctx.close(); - } - } catch (NamingException e) { - passed = true; - e.printStackTrace(); - } - - if (!passed) { - throw new Exception("Read timeout test failed," + - " read timeout exception not thrown"); - } - System.out.println("The test PASSED"); - } - - static class Server implements Runnable, Closeable { - private final ServerSocket ss; - - private Server(ServerSocket ss) { - this.ss = ss; - } - - static Server create() throws IOException { - Server server = new Server(new ServerSocket(0)); - new Thread(server).start(); - return server; - } - - int port() { - return ss.getLocalPort(); - } - - public void run() { - try (Socket s = ss.accept()) { - System.out.println("Server: Connection accepted"); - BufferedInputStream bis = new BufferedInputStream(s.getInputStream()); - byte[] buf = new byte[100]; - int n; - do { - n = bis.read(buf); - } while (n > 0); - } catch (IOException e) { - // ignore - } - } - - public void close() throws IOException { - ss.close(); - } - } -}