1 /* 2 * Copyright (c) 2018, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import javax.naming.Context; 25 import javax.naming.NamingException; 26 import javax.naming.directory.DirContext; 27 import javax.naming.directory.InitialDirContext; 28 import java.io.Closeable; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.OutputStream; 32 import java.net.ServerSocket; 33 import java.net.Socket; 34 import java.util.Hashtable; 35 36 /* 37 * @test 38 * @bug 8205330 39 * @summary Test that If a connection has already been established and then 40 * the LDAP directory server sends an (unsolicited) 41 * "Notice of Disconnection", make sure client handle it correctly, 42 * no NPE been thrown. 43 * @run main/othervm DisconnectNPETest 44 */ 45 46 public class DisconnectNPETest { 47 // Normally the NPE bug should be hit less than 100 times run, but just in 48 // case, we set repeat count to 1000 here. 49 private static final int REPEAT_COUNT = 1000; 50 51 public static void main(String[] args) throws IOException { 52 new DisconnectNPETest().run(); 53 } 54 55 private ServerSocket serverSocket; 56 private Hashtable<Object, Object> env; 57 private TestLDAPServer server; 58 59 private void initRes() throws IOException { 60 serverSocket = new ServerSocket(0); 61 server = new TestLDAPServer(); 62 server.start(); 63 } 64 65 private void initTest() { 66 env = new Hashtable<>(); 67 env.put(Context.INITIAL_CONTEXT_FACTORY, 68 "com.sun.jndi.ldap.LdapCtxFactory"); 69 env.put(Context.PROVIDER_URL, String.format("ldap://localhost:%d/", 70 serverSocket.getLocalPort())); 71 env.put(Context.SECURITY_AUTHENTICATION, "simple"); 72 env.put(Context.SECURITY_PRINCIPAL, 73 "cn=8205330,ou=Client6,ou=Vendor1,o=IMC,c=US"); 74 env.put(Context.SECURITY_CREDENTIALS, "secret123"); 75 } 76 77 private void run() throws IOException { 78 initRes(); 79 initTest(); 80 int count = 0; 81 try { 82 while (count < REPEAT_COUNT) { 83 count++; 84 InitialDirContext context = null; 85 try { 86 context = new InitialDirContext(env); 87 } catch (NamingException ne) { 88 System.out.println("(" + count + "/" + REPEAT_COUNT 89 + ") It's ok to get NamingException: " + ne); 90 // for debug 91 ne.printStackTrace(); 92 } finally { 93 cleanupContext(context); 94 } 95 } 96 } finally { 97 System.out.println("Test count: " + count + "/" + REPEAT_COUNT); 98 cleanupTest(); 99 } 100 } 101 102 private void cleanupTest() { 103 if (server != null) { 104 server.stopServer(); 105 } 106 cleanupClosableRes(serverSocket); 107 } 108 109 private void cleanupContext(DirContext context) { 110 if (context != null) { 111 try { 112 context.close(); 113 } catch (NamingException e) { 114 // ignore 115 } 116 } 117 } 118 119 private static void cleanupClosableRes(Closeable res) { 120 if (res != null) { 121 try { 122 res.close(); 123 } catch (Exception e) { 124 // ignore 125 } 126 } 127 } 128 129 class TestLDAPServer extends Thread { 130 private volatile boolean isRunning; 131 132 TestLDAPServer() { 133 isRunning = true; 134 } 135 136 private void stopServer() { 137 isRunning = false; 138 } 139 140 @Override 141 public void run() { 142 try { 143 while (isRunning) { 144 Socket clientSocket = serverSocket.accept(); 145 Thread handler = new Thread( 146 new LDAPServerHandler(clientSocket)); 147 handler.start(); 148 } 149 } catch (IOException e) { 150 if (isRunning) { 151 throw new RuntimeException(e); 152 } 153 } 154 } 155 } 156 157 static class LDAPServerHandler implements Runnable { 158 // "Notice of Disconnection" message 159 private static final byte[] DISCONNECT_MSG = { 0x30, 0x4C, 0x02, 0x01, 160 0x00, 0x78, 0x47, 0x0A, 0x01, 0x34, 0x04, 0x00, 0x04, 0x28, 161 0x55, 0x4E, 0x41, 0x56, 0x41, 0x49, 0x4C, 0x41, 0x42, 0x4C, 162 0x45, 0x3A, 0x20, 0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72, 163 0x76, 0x65, 0x72, 0x20, 0x77, 0x69, 0x6C, 0x6C, 0x20, 0x64, 164 0x69, 0x73, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x21, 165 (byte) 0x8A, 0x16, 0x31, 0x2E, 0x33, 0x2E, 0x36, 0x2E, 0x31, 166 0x2E, 0x34, 0x2E, 0x31, 0x2E, 0x31, 0x34, 0x36, 0x36, 0x2E, 167 0x32, 0x30, 0x30, 0x33, 0x36 }; 168 private static final byte[] BIND_RESPONSE = { 0x30, 0x0C, 0x02, 0x01, 169 0x01, 0x61, 0x07, 0x0A, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00 }; 170 private final Socket clientSocket; 171 172 private LDAPServerHandler(final Socket clientSocket) { 173 this.clientSocket = clientSocket; 174 } 175 176 @Override 177 public void run() { 178 try (clientSocket; 179 OutputStream out = clientSocket.getOutputStream(); 180 InputStream in = clientSocket.getInputStream()) { 181 if (in.read() > 0) { 182 in.skip(in.available()); 183 out.write(BIND_RESPONSE); 184 out.write(DISCONNECT_MSG); 185 } 186 } catch (IOException e) { 187 e.printStackTrace(); 188 } 189 } 190 } 191 }