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 com.sun.jndi.ldap.LdapURL;
25 
26 import javax.naming.Context;
27 import javax.naming.NamingEnumeration;
28 import javax.naming.NamingException;
29 import javax.naming.directory.Attribute;
30 import javax.naming.directory.Attributes;
31 import javax.naming.directory.DirContext;
32 import javax.naming.directory.SearchResult;
33 import java.io.FileNotFoundException;
34 import java.io.PrintStream;
35 import java.net.ServerSocket;
36 import java.nio.file.Files;
37 import java.nio.file.Path;
38 import java.nio.file.Paths;
39 import java.util.Enumeration;
40 import java.util.Hashtable;
41 import java.util.Vector;
42 
43 public class LDAPTestUtils {
44     public static final String TEST_LDAP_SERVER_THREAD = "test.ldap.server.thread";
45     public static final int CERTS_LOOKUP_MAX_DEPTH = 4;
46 
47     protected static boolean debug = true;
48 
49     /*
50      * Process command line arguments and return properties in a Hashtable.
51      */
initEnv(String testname, String[] args)52     public static Hashtable<Object, Object> initEnv(String testname,
53             String[] args) {
54         return initEnv(null, testname, args, false);
55     }
56 
initEnv(ServerSocket socket, String testname, String[] args, boolean authInfo)57     public static Hashtable<Object, Object> initEnv(ServerSocket socket,
58             String testname, String[] args, boolean authInfo) {
59 
60         Hashtable<Object, Object> env = new Hashtable<>();
61         String root = "o=IMC,c=US";
62         String vendor = "Vendor1";
63         String client = "Client1";
64         String realm = "";
65         Vector<String> refs = new Vector<>();
66 
67         // set defaults for some JNDI properties
68         env.put(Context.INITIAL_CONTEXT_FACTORY,
69                 "com.sun.jndi.ldap.LdapCtxFactory");
70 
71         if (authInfo) {
72             env.put(Context.SECURITY_AUTHENTICATION, "simple");
73             env.put(Context.SECURITY_PRINCIPAL, "cn=admin,o=IMC,c=US");
74             env.put(Context.SECURITY_CREDENTIALS, "secret99");
75         }
76 
77         env.put("root", root);
78         env.put("vendor", vendor);
79         env.put("client", client);
80 
81         boolean traceEnable = false;
82         for (int i = 0; i < args.length; i++) {
83             if (args[i].equals("-D") && (args.length > i + 1)) {
84                 extractProperty(args[++i], env);
85             } else if (args[i].startsWith("-D")) {
86                 extractProperty(args[i].substring(2), env);
87             } else if (args[i].equals("-referral") && (args.length > i + 1)) {
88                 refs.addElement(args[++i]);
89             } else if (args[i].equals("-trace")) {
90                 traceEnable = true;
91             }
92         }
93 
94         env.put("disabled.realm", realm);
95 
96         if (refs.size() > 0) {
97             env.put("referrals", refs);
98         }
99 
100         if (traceEnable) {
101             enableLDAPTrace(env, testname);
102         } else {
103             if (socket != null) {
104                 env.put(TEST_LDAP_SERVER_THREAD,
105                         startLDAPServer(socket, getCaptureFile(testname)));
106                 env.put("java.naming.provider.url",
107                         "ldap://localhost:" + socket.getLocalPort());
108             } else {
109                 // for tests which run against remote server or no server
110                 // required
111                 debug("Skip local LDAP Server creation "
112                         + "since ServerSocket is null");
113             }
114         }
115 
116         return env;
117     }
118 
119     /*
120      * Clean-up the directory context.
121      */
cleanup(DirContext ctx)122     public static void cleanup(DirContext ctx) {
123         if (ctx != null) {
124             try {
125                 ctx.close();
126             } catch (NamingException e) {
127                 // ignore
128             }
129         }
130     }
131 
132     /*
133      * Clean-up the sub context.
134      */
cleanupSubcontext(DirContext ctx, String name)135     public static void cleanupSubcontext(DirContext ctx, String name) {
136         if (ctx != null) {
137             try {
138                 ctx.destroySubcontext(name);
139             } catch (NamingException ne) {
140                 // ignore
141             }
142         }
143     }
144 
145     /*
146      * Assemble a distinguished name from the base components and the
147      * namespace root.
148      *
149      * The components are prefixed with 'dc=' if the root is a DC-style name.
150      * Otherwise they are prefixed with 'ou='.
151      */
buildDN(String[] bases, String root)152     public static String buildDN(String[] bases, String root) {
153 
154         StringBuilder dn = new StringBuilder();
155         String prefix;
156 
157         if (!root.contains("dc=")) {
158             prefix = "ou=";
159         } else {
160             prefix = "dc=";
161         }
162 
163         for (String base : bases) {
164             dn.append(prefix).append(base).append(",");
165         }
166 
167         return dn.append(root).toString();
168     }
169 
170     /*
171      * Scan the results to confirm that the expected name is present.
172      */
checkResult(NamingEnumeration results, String name)173     public static int checkResult(NamingEnumeration results, String name)
174             throws NamingException {
175 
176         return checkResult(results, new String[] { name }, null);
177     }
178 
179     /*
180      * Scan the results to confirm that the expected names and attributes
181      * are present.
182      */
checkResult(NamingEnumeration results, String[] names, Attributes attrs)183     public static int checkResult(NamingEnumeration results, String[] names,
184             Attributes attrs) throws NamingException {
185 
186         int found = 0;
187 
188         while (results != null && results.hasMore()) {
189 
190             SearchResult entry = (SearchResult) results.next();
191             String entryDN = entry.getName();
192 
193             debug(">>> received: " + entryDN);
194 
195             if (entry.isRelative()) {
196                 entryDN = entryDN.toLowerCase(); // normalize
197             } else {
198                 LdapURL url = new LdapURL(entryDN); // extract DN
199                 entryDN = url.getDN().toLowerCase(); // normalize
200             }
201 
202             for (String name : names) {
203                 if ((entryDN.contains(name.toLowerCase())) || (entryDN
204                         .equalsIgnoreCase(name))) {
205 
206                     debug(">>> checked results: found '" + name + "'");
207 
208                     if (attrs == null || foundAttributes(entry, attrs)) {
209                         found++;
210                         break;
211                     }
212                 }
213             }
214         }
215 
216         debug(">>> checked results: found " + found
217                 + " entries that meet the criteria.");
218 
219         return found;
220     }
221 
222     /*
223      * Confirm that the attributes are present in the entry.
224      */
foundAttributes(SearchResult entry, Attributes attrs)225     public static boolean foundAttributes(SearchResult entry, Attributes attrs)
226             throws NamingException {
227 
228         Attributes eattrs = entry.getAttributes();
229         int found = 0;
230 
231         if ((eattrs == null) || (attrs == null)) {
232             return false;
233         }
234 
235         for (NamingEnumeration ne = attrs.getAll(); ne.hasMoreElements(); ) {
236 
237             Attribute attr = (Attribute) ne.next();
238 
239             if (equalsIgnoreCase(eattrs.get(attr.getID()), attr)) {
240                 found++;
241             } else {
242                 debug(">>> foundAttributes: no match for " + attr.getID());
243             }
244         }
245         debug(">>> foundAttributes: found " + found + " attributes");
246         return (found == attrs.size());
247     }
248 
startLDAPServer(ServerSocket serverSocket, String fileName)249     public static Thread startLDAPServer(ServerSocket serverSocket,
250             String fileName) {
251         if (serverSocket == null) {
252             throw new RuntimeException("Error: failed to create LDAPServer "
253                     + "since ServerSocket is null");
254         }
255 
256         if (!Files.exists(Paths.get(fileName))) {
257             throw new RuntimeException(
258                     "Error: failed to create LDAPServer, not found ldap "
259                             + "cache file " + fileName);
260         }
261 
262         Thread thread = new Thread(() -> {
263             try {
264                 new test.LDAPServer(serverSocket, fileName);
265             } catch (Exception e) {
266                 System.out.println("Warning: LDAP server running with issue");
267                 e.printStackTrace();
268             }
269         });
270 
271         thread.start();
272         return thread;
273     }
274 
equalsIgnoreCase(Attribute received, Attribute expected)275     private static boolean equalsIgnoreCase(Attribute received,
276             Attribute expected) {
277 
278         if (received == null || !received.getID()
279                 .equalsIgnoreCase(expected.getID())) {
280             return false;
281         }
282 
283         try {
284 
285             Enumeration expectedVals = expected.getAll();
286             Object obj;
287             while (expectedVals.hasMoreElements()) {
288                 obj = expectedVals.nextElement();
289                 if (!received.contains(obj)) {
290                     if (!(obj instanceof String)) {
291                         return false;
292                     }
293                     if (!received.contains(((String) obj).toLowerCase())) {
294                         return false;
295                     }
296                 }
297             }
298 
299         } catch (NamingException e) {
300             return false;
301         }
302 
303         return true;
304     }
305 
extractProperty(String propString, Hashtable<Object, Object> env)306     private static void extractProperty(String propString,
307             Hashtable<Object, Object> env) {
308         int index;
309 
310         if ((index = propString.indexOf('=')) > 0) {
311             env.put(propString.substring(0, index),
312                     propString.substring(index + 1));
313         } else {
314             throw new RuntimeException(
315                     "Failed to extract test args property from " + propString);
316         }
317     }
318 
enableLDAPTrace(Hashtable<Object, Object> env, String testname)319     private static void enableLDAPTrace(Hashtable<Object, Object> env,
320             String testname) {
321         try {
322             PrintStream outStream = new PrintStream(getCaptureFile(testname));
323             env.put("com.sun.jndi.ldap.trace.ber", outStream);
324         } catch (FileNotFoundException e) {
325             throw new RuntimeException(
326                     "Error: failed to enable ldap trace: " + e.getMessage(), e);
327         }
328     }
329 
getCaptureFile(String testname)330     private static String getCaptureFile(String testname) {
331         return Paths.get(System.getProperty("test.src"))
332                 .resolve(testname + ".ldap").toString();
333     }
334 
debug(Object object)335     public static void debug(Object object) {
336         if (debug) {
337             System.out.println(object);
338         }
339     }
340 
findCertsHome(int depth)341     public static String findCertsHome(int depth) {
342         Path path = Paths.get(System.getProperty("test.src", "."))
343                 .toAbsolutePath();
344         for (int i = depth; i >= 0; i--) {
345             Path homePath = path.resolve("certs");
346             if (Files.exists(homePath) && Files.isDirectory(homePath)) {
347                 return homePath.toString();
348             }
349 
350             path = path.getParent();
351             if (path == null) {
352                 break;
353             }
354         }
355 
356         return System.getProperty("test.src", ".");
357     }
358 }
359