Wed, 09 Sep 2020 14:19:14 -0400
8062947: Fix exception message to correctly represent LDAP connection failure
Reviewed-by: dfuchs, xyin, vtewari
1.1 --- a/src/share/classes/com/sun/jndi/ldap/Connection.java Wed Sep 09 14:18:45 2020 -0400 1.2 +++ b/src/share/classes/com/sun/jndi/ldap/Connection.java Wed Sep 09 14:19:14 2020 -0400 1.3 @@ -1,5 +1,5 @@ 1.4 /* 1.5 - * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved. 1.6 + * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved. 1.7 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1.8 * 1.9 * This code is free software; you can redistribute it and/or modify it 1.10 @@ -454,22 +454,34 @@ 1.11 BerDecoder readReply(LdapRequest ldr) throws IOException, NamingException { 1.12 BerDecoder rber; 1.13 1.14 + NamingException namingException = null; 1.15 try { 1.16 // if no timeout is set so we wait infinitely until 1.17 - // a response is received 1.18 + // a response is received OR until the connection is closed or cancelled 1.19 // http://docs.oracle.com/javase/8/docs/technotes/guides/jndi/jndi-ldap.html#PROP 1.20 rber = ldr.getReplyBer(readTimeout); 1.21 } catch (InterruptedException ex) { 1.22 throw new InterruptedNamingException( 1.23 "Interrupted during LDAP operation"); 1.24 + } catch (CommunicationException ce) { 1.25 + // Re-throw 1.26 + throw ce; 1.27 + } catch (NamingException ne) { 1.28 + // Connection is timed out OR closed/cancelled 1.29 + namingException = ne; 1.30 + rber = null; 1.31 } 1.32 1.33 if (rber == null) { 1.34 abandonRequest(ldr, null); 1.35 - throw new NamingException( 1.36 - "LDAP response read timed out, timeout used:" 1.37 - + readTimeout + "ms." ); 1.38 - 1.39 + } 1.40 + // namingException can be not null in the following cases: 1.41 + // a) The response is timed-out 1.42 + // b) LDAP request connection has been closed or cancelled 1.43 + // The exception message is initialized in LdapRequest::getReplyBer 1.44 + if (namingException != null) { 1.45 + // Re-throw NamingException after all cleanups are done 1.46 + throw namingException; 1.47 } 1.48 return rber; 1.49 } 1.50 @@ -888,15 +900,6 @@ 1.51 inbuf = Arrays.copyOf(inbuf, offset + left.length); 1.52 System.arraycopy(left, 0, inbuf, offset, left.length); 1.53 offset += left.length; 1.54 -/* 1.55 -if (dump > 0) { 1.56 -System.err.println("seqlen: " + seqlen); 1.57 -System.err.println("bufsize: " + offset); 1.58 -System.err.println("bytesleft: " + bytesleft); 1.59 -System.err.println("bytesread: " + bytesread); 1.60 -} 1.61 -*/ 1.62 - 1.63 1.64 try { 1.65 retBer = new BerDecoder(inbuf, 0, offset); 1.66 @@ -1004,36 +1007,4 @@ 1.67 } 1.68 return buf; 1.69 } 1.70 - 1.71 - // This code must be uncommented to run the LdapAbandonTest. 1.72 - /*public void sendSearchReqs(String dn, int numReqs) { 1.73 - int i; 1.74 - String attrs[] = null; 1.75 - for(i = 1; i <= numReqs; i++) { 1.76 - BerEncoder ber = new BerEncoder(2048); 1.77 - 1.78 - try { 1.79 - ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); 1.80 - ber.encodeInt(i); 1.81 - ber.beginSeq(LdapClient.LDAP_REQ_SEARCH); 1.82 - ber.encodeString(dn == null ? "" : dn); 1.83 - ber.encodeInt(0, LdapClient.LBER_ENUMERATED); 1.84 - ber.encodeInt(3, LdapClient.LBER_ENUMERATED); 1.85 - ber.encodeInt(0); 1.86 - ber.encodeInt(0); 1.87 - ber.encodeBoolean(true); 1.88 - LdapClient.encodeFilter(ber, ""); 1.89 - ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); 1.90 - ber.encodeStringArray(attrs); 1.91 - ber.endSeq(); 1.92 - ber.endSeq(); 1.93 - ber.endSeq(); 1.94 - writeRequest(ber, i); 1.95 - //System.err.println("wrote request " + i); 1.96 - } catch (Exception ex) { 1.97 - //System.err.println("ldap.search: Caught " + ex + " building req"); 1.98 - } 1.99 - 1.100 - } 1.101 - } */ 1.102 }
2.1 --- a/src/share/classes/com/sun/jndi/ldap/LdapRequest.java Wed Sep 09 14:18:45 2020 -0400 2.2 +++ b/src/share/classes/com/sun/jndi/ldap/LdapRequest.java Wed Sep 09 14:19:14 2020 -0400 2.3 @@ -1,5 +1,5 @@ 2.4 /* 2.5 - * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. 2.6 + * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved. 2.7 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 2.8 * 2.9 * This code is free software; you can redistribute it and/or modify it 2.10 @@ -29,11 +29,14 @@ 2.11 import java.util.concurrent.BlockingQueue; 2.12 import java.util.concurrent.LinkedBlockingQueue; 2.13 import javax.naming.CommunicationException; 2.14 +import javax.naming.NamingException; 2.15 import java.util.concurrent.TimeUnit; 2.16 2.17 final class LdapRequest { 2.18 2.19 private final static BerDecoder EOF = new BerDecoder(new byte[]{}, -1, 0); 2.20 + private final static String CLOSE_MSG = "LDAP connection has been closed"; 2.21 + private final static String TIMEOUT_MSG_FMT = "LDAP response read timed out, timeout used: %d ms."; 2.22 2.23 LdapRequest next; // Set/read in synchronized Connection methods 2.24 final int msgId; // read-only 2.25 @@ -95,14 +98,22 @@ 2.26 return pauseAfterReceipt; 2.27 } 2.28 2.29 - BerDecoder getReplyBer(long millis) throws CommunicationException, 2.30 + /** 2.31 + * Read reply BER 2.32 + * @param millis timeout, infinite if the value is negative 2.33 + * @return BerDecoder if reply was read successfully 2.34 + * @throws CommunicationException request has been canceled and request does not need to be abandoned 2.35 + * @throws NamingException request has been closed or timed out. Request does need to be abandoned 2.36 + * @throws InterruptedException LDAP operation has been interrupted 2.37 + */ 2.38 + BerDecoder getReplyBer(long millis) throws NamingException, 2.39 InterruptedException { 2.40 if (cancelled) { 2.41 throw new CommunicationException("Request: " + msgId + 2.42 " cancelled"); 2.43 } 2.44 if (isClosed()) { 2.45 - return null; 2.46 + throw new NamingException(CLOSE_MSG); 2.47 } 2.48 2.49 BerDecoder result = millis > 0 ? 2.50 @@ -113,7 +124,15 @@ 2.51 " cancelled"); 2.52 } 2.53 2.54 - return result == EOF ? null : result; 2.55 + // poll from 'replies' blocking queue ended-up with timeout 2.56 + if (result == null) { 2.57 + throw new NamingException(String.format(TIMEOUT_MSG_FMT, millis)); 2.58 + } 2.59 + // Unexpected EOF can be caused by connection closure or cancellation 2.60 + if (result == EOF) { 2.61 + throw new NamingException(CLOSE_MSG); 2.62 + } 2.63 + return result; 2.64 } 2.65 2.66 boolean hasSearchCompleted() {
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/test/com/sun/jndi/ldap/NamingExceptionMessageTest.java Wed Sep 09 14:19:14 2020 -0400 3.3 @@ -0,0 +1,165 @@ 3.4 +/* 3.5 + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. 3.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 3.7 + * 3.8 + * This code is free software; you can redistribute it and/or modify it 3.9 + * under the terms of the GNU General Public License version 2 only, as 3.10 + * published by the Free Software Foundation. 3.11 + * 3.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 3.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 3.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 3.15 + * version 2 for more details (a copy is included in the LICENSE file that 3.16 + * accompanied this code). 3.17 + * 3.18 + * You should have received a copy of the GNU General Public License version 3.19 + * 2 along with this work; if not, write to the Free Software Foundation, 3.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 3.21 + * 3.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 3.23 + * or visit www.oracle.com if you need additional information or have any 3.24 + * questions. 3.25 + */ 3.26 + 3.27 +/* 3.28 + * @test 3.29 + * @bug 8062947 3.30 + * @summary Test that NamingException message text matches the failure reason 3.31 + * @library lib/ 3.32 + * @library /lib/testlibrary 3.33 + * @run testng NamingExceptionMessageTest 3.34 + */ 3.35 + 3.36 +import javax.naming.Context; 3.37 +import javax.naming.NamingException; 3.38 +import javax.naming.directory.InitialDirContext; 3.39 +import java.io.IOException; 3.40 +import java.io.OutputStream; 3.41 +import java.net.InetAddress; 3.42 +import java.net.InetSocketAddress; 3.43 +import java.net.ServerSocket; 3.44 +import java.net.Socket; 3.45 +import java.util.Hashtable; 3.46 +import java.util.concurrent.CountDownLatch; 3.47 +import java.util.concurrent.TimeUnit; 3.48 + 3.49 +import org.testng.annotations.Test; 3.50 +import org.testng.Assert; 3.51 +import jdk.testlibrary.net.URIBuilder; 3.52 + 3.53 +public class NamingExceptionMessageTest { 3.54 + 3.55 + @Test 3.56 + public void timeoutMessageTest() throws Exception { 3.57 + try (TestLdapServer ldapServer = TestLdapServer.newInstance(false)) { 3.58 + ldapServer.start(); 3.59 + ldapServer.awaitStartup(); 3.60 + Hashtable<Object, Object> env = ldapServer.getInitialLdapCtxEnvironment(TIMEOUT_VALUE); 3.61 + Exception namingException = Assert.expectThrows(NamingException.class, () -> new InitialDirContext(env)); 3.62 + System.out.println("Got naming exception:" + namingException); 3.63 + Assert.assertEquals(namingException.getMessage(), EXPECTED_TIMEOUT_MESSAGE); 3.64 + } 3.65 + } 3.66 + 3.67 + @Test 3.68 + public void connectionClosureMessageTest() throws Exception { 3.69 + try (TestLdapServer ldapServer = TestLdapServer.newInstance(true)) { 3.70 + ldapServer.start(); 3.71 + ldapServer.awaitStartup(); 3.72 + Hashtable<Object, Object> env = ldapServer.getInitialLdapCtxEnvironment(0); 3.73 + Exception namingException = Assert.expectThrows(NamingException.class, () -> new InitialDirContext(env)); 3.74 + System.out.println("Got naming exception:" + namingException); 3.75 + Assert.assertEquals(namingException.getMessage(), EXPECTED_CLOSURE_MESSAGE); 3.76 + } 3.77 + } 3.78 + 3.79 + // Test LDAP server 3.80 + private static class TestLdapServer extends BaseLdapServer { 3.81 + 3.82 + private final boolean closeConnections; 3.83 + private final CountDownLatch startupLatch = new CountDownLatch(1); 3.84 + 3.85 + public Hashtable<Object, Object> getInitialLdapCtxEnvironment(int readTimeoutValue) { 3.86 + // Create environment for initial LDAP context 3.87 + Hashtable<Object, Object> env = new Hashtable<>(); 3.88 + 3.89 + // Activate LDAPv3 3.90 + env.put("java.naming.ldap.version", "3"); 3.91 + 3.92 + // De-activate the ManageDsaIT control 3.93 + env.put(Context.REFERRAL, "follow"); 3.94 + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); 3.95 + env.put(Context.PROVIDER_URL, getUrlString()); 3.96 + env.put(Context.SECURITY_AUTHENTICATION, "simple"); 3.97 + env.put(Context.SECURITY_PRINCIPAL, "name"); 3.98 + env.put(Context.SECURITY_CREDENTIALS, "pwd"); 3.99 + 3.100 + if (readTimeoutValue > 0) { 3.101 + env.put("com.sun.jndi.ldap.read.timeout", String.valueOf(readTimeoutValue)); 3.102 + env.put("com.sun.jndi.ldap.connect.timeout", String.valueOf(readTimeoutValue)); 3.103 + } 3.104 + 3.105 + return env; 3.106 + } 3.107 + 3.108 + private String getUrlString() { 3.109 + String url = URIBuilder.newBuilder() 3.110 + .scheme("ldap") 3.111 + .loopback() 3.112 + .port(getPort()) 3.113 + .buildUnchecked() 3.114 + .toString(); 3.115 + return url; 3.116 + } 3.117 + 3.118 + public static TestLdapServer newInstance(boolean closeConnections) throws IOException { 3.119 + ServerSocket srvSock = new ServerSocket(); 3.120 + srvSock.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); 3.121 + return new TestLdapServer(srvSock, closeConnections); 3.122 + } 3.123 + 3.124 + void awaitStartup() throws InterruptedException { 3.125 + startupLatch.await(); 3.126 + } 3.127 + 3.128 + private TestLdapServer(ServerSocket serverSocket, boolean closeConnections) { 3.129 + super(serverSocket); 3.130 + this.closeConnections = closeConnections; 3.131 + 3.132 + } 3.133 + 3.134 + @Override 3.135 + protected void beforeAcceptingConnections() { 3.136 + startupLatch.countDown(); 3.137 + } 3.138 + 3.139 + @Override 3.140 + protected void handleRequest(Socket socket, 3.141 + LdapMessage msg, 3.142 + OutputStream out) 3.143 + throws IOException { 3.144 + switch (msg.getOperation()) { 3.145 + case BIND_REQUEST: 3.146 + if (closeConnections) { 3.147 + closeSilently(socket); 3.148 + } else { 3.149 + try { 3.150 + TimeUnit.DAYS.sleep(Integer.MAX_VALUE); 3.151 + } catch (InterruptedException e) { 3.152 + Thread.currentThread().interrupt(); 3.153 + } 3.154 + } 3.155 + default: 3.156 + break; 3.157 + } 3.158 + } 3.159 + } 3.160 + 3.161 + // Expected message for case when connection is closed on server side 3.162 + private static final String EXPECTED_CLOSURE_MESSAGE = "LDAP connection has been closed"; 3.163 + // read and connect timeouts value 3.164 + private static final int TIMEOUT_VALUE = 129; 3.165 + // Expected message text when connection is timed-out 3.166 + private static final String EXPECTED_TIMEOUT_MESSAGE = String.format( 3.167 + "LDAP response read timed out, timeout used: %d ms.", TIMEOUT_VALUE); 3.168 +}