1 /*
2  * Copyright (c) 2011, 2017, 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 import java.io.BufferedInputStream;
24 import java.io.IOException;
25 import java.io.OutputStreamWriter;
26 import java.io.PrintWriter;
27 import java.net.ServerSocket;
28 import java.net.Socket;
29 import java.nio.charset.StandardCharsets;
30 import java.util.ConcurrentModificationException;
31 import java.util.Hashtable;
32 import javax.naming.Context;
33 import javax.naming.InitialContext;
34 import javax.naming.NamingException;
35 import javax.naming.event.EventContext;
36 import javax.naming.event.NamingEvent;
37 import javax.naming.event.NamingExceptionEvent;
38 import javax.naming.event.NamingListener;
39 import javax.naming.event.ObjectChangeListener;
40 
41 /**
42  * @test
43  * @bug 8176192
44  * @summary Incorrect usage of Iterator in Java 8 In com.sun.jndi.ldap.
45  * EventSupport.removeNamingListener
46  * @modules java.naming
47  * @run main RemoveNamingListenerTest
48  */
49 public class RemoveNamingListenerTest {
50 
51     private static volatile Exception exception;
52 
main(String args[])53     public static void main(String args[]) throws Exception {
54         // start the LDAP server
55         TestLDAPServer server = new TestLDAPServer();
56         server.start();
57 
58         // Set up environment for creating initial context
59         Hashtable<String, Object> env = new Hashtable<>(3);
60         env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
61         env.put(Context.PROVIDER_URL, "ldap://localhost:" + server.getPort() + "/o=example");
62         env.put("com.sun.jndi.ldap.connect.timeout", "2000");
63         EventContext ctx = null;
64 
65         try {
66             ctx = (EventContext) (new InitialContext(env).lookup(""));
67             String target = "cn=Vyom Tewari";
68 
69             // Create listeners
70             NamingListener oneListener = new SampleListener();
71             NamingListener objListener = new SampleListener();
72             NamingListener subListener = new SampleListener();
73 
74             // Register listeners using different scopes
75             ctx.addNamingListener(target, EventContext.ONELEVEL_SCOPE, oneListener);
76             ctx.addNamingListener(target, EventContext.OBJECT_SCOPE, objListener);
77             ctx.addNamingListener(target, EventContext.SUBTREE_SCOPE, subListener);
78 
79             //remove a listener in different thread
80             Thread t = new Thread(new RemoveNamingListener(ctx, subListener));
81             t.start();
82             t.join();
83 
84             if (exception != null) {
85                 throw exception;
86             }
87             System.out.println("Test run OK!!!");
88         } finally {
89             if (ctx != null) {
90                 ctx.close();
91             }
92             server.stopServer();
93         }
94     }
95 
96     /**
97      * Helper thread that removes the naming listener.
98      */
99     static class RemoveNamingListener implements Runnable {
100 
101         final EventContext ctx;
102         final NamingListener listener;
103 
RemoveNamingListener(EventContext ctx, NamingListener listener)104         RemoveNamingListener(EventContext ctx, NamingListener listener) {
105             this.ctx = ctx;
106             this.listener = listener;
107         }
108 
109         @Override
run()110         public void run() {
111             try {
112                 ctx.removeNamingListener(listener);
113             } catch (NamingException | ConcurrentModificationException ex) {
114                 exception = ex;
115             }
116         }
117     }
118 
119     static class SampleListener implements ObjectChangeListener {
120 
121         @Override
objectChanged(NamingEvent ne)122         public void objectChanged(NamingEvent ne) {
123             //do nothing
124         }
125 
126         @Override
namingExceptionThrown(NamingExceptionEvent nee)127         public void namingExceptionThrown(NamingExceptionEvent nee) {
128             //do nothing
129         }
130     }
131 }
132 
133 class TestLDAPServer extends Thread {
134 
135     private final int LDAP_PORT;
136     private final ServerSocket serverSocket;
137     private volatile boolean isRunning;
138 
TestLDAPServer()139     TestLDAPServer() throws IOException {
140         serverSocket = new ServerSocket(0);
141         isRunning = true;
142         LDAP_PORT = serverSocket.getLocalPort();
143         setDaemon(true);
144     }
145 
getPort()146     public int getPort() {
147         return LDAP_PORT;
148     }
149 
stopServer()150     public void stopServer() {
151         isRunning = false;
152         if (serverSocket != null && !serverSocket.isClosed()) {
153             try {
154                 // this will cause ServerSocket.accept() to throw SocketException.
155                 serverSocket.close();
156             } catch (IOException ignored) {
157             }
158         }
159     }
160 
161     @Override
run()162     public void run() {
163         try {
164             while (isRunning) {
165                 Socket clientSocket = serverSocket.accept();
166                 Thread handler = new Thread(new LDAPServerHandler(clientSocket));
167                 handler.setDaemon(true);
168                 handler.start();
169             }
170         } catch (IOException iOException) {
171             //do not throw exception if server is not running.
172             if (isRunning) {
173                 throw new RuntimeException(iOException);
174             }
175         } finally {
176             stopServer();
177         }
178     }
179 }
180 
181 class LDAPServerHandler implements Runnable {
182 
183     private final Socket clientSocket;
184 
LDAPServerHandler(final Socket clientSocket)185     public LDAPServerHandler(final Socket clientSocket) {
186         this.clientSocket = clientSocket;
187     }
188 
189     @Override
run()190     public void run() {
191         BufferedInputStream in = null;
192         PrintWriter out = null;
193         byte[] bindResponse = {0x30, 0x0C, 0x02, 0x01, 0x01, 0x61, 0x07, 0x0A, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00};
194         byte[] searchResponse = {0x30, 0x0C, 0x02, 0x01, 0x02, 0x65, 0x07, 0x0A, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00};
195         try {
196             in = new BufferedInputStream(clientSocket.getInputStream());
197             out = new PrintWriter(new OutputStreamWriter(
198                     clientSocket.getOutputStream(), StandardCharsets.UTF_8), true);
199             while (true) {
200 
201                 // Read the LDAP BindRequest
202                 while (in.read() != -1) {
203                     in.skip(in.available());
204                     break;
205                 }
206 
207                 // Write an LDAP BindResponse
208                 out.write(new String(bindResponse));
209                 out.flush();
210 
211                 // Read the LDAP SearchRequest
212                 while (in.read() != -1) {
213                     in.skip(in.available());
214                     break;
215                 }
216 
217                 // Write an LDAP SearchResponse
218                 out.write(new String(searchResponse));
219                 out.flush();
220             }
221         } catch (IOException iOException) {
222             throw new RuntimeException(iOException);
223         } finally {
224             if (in != null) {
225                 try {
226                     in.close();
227                 } catch (IOException ignored) {
228                 }
229             }
230             if (out != null) {
231                 out.close();
232             }
233             if (clientSocket != null) {
234                 try {
235                     clientSocket.close();
236                 } catch (IOException ignored) {
237                 }
238             }
239         }
240     }
241 }
242