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 }