1 /*
   2  * Copyright (c) 1999, 2013, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.jndi.ldap;
  27 
  28 import java.util.*;
  29 
  30 import javax.naming.*;
  31 import javax.naming.directory.*;
  32 import javax.naming.spi.ldap.LdapDnsProviderResult;
  33 import javax.naming.spi.ObjectFactory;
  34 import javax.naming.spi.InitialContextFactory;
  35 import javax.naming.ldap.Control;
  36 
  37 import com.sun.jndi.url.ldap.ldapURLContextFactory;
  38 
  39 final public class LdapCtxFactory implements ObjectFactory, InitialContextFactory {
  40     /**
  41      * The type of each address in an LDAP reference.
  42      */
  43     public final static String ADDRESS_TYPE = "URL";
  44 
  45     // ----------------- ObjectFactory interface --------------------
  46 
  47     public Object getObjectInstance(Object ref, Name name, Context nameCtx,
  48         Hashtable<?,?> env) throws Exception {
  49 
  50         if (!isLdapRef(ref)) {
  51             return null;
  52         }
  53         ObjectFactory factory = new ldapURLContextFactory();
  54         String[] urls = getURLs((Reference)ref);
  55         return factory.getObjectInstance(urls, name, nameCtx, env);
  56     }
  57 
  58     // ----------------- InitialContext interface  --------------------
  59 
  60     public Context getInitialContext(Hashtable<?,?> envprops)
  61         throws NamingException {
  62 
  63         try {
  64             String providerUrl = (envprops != null) ?
  65                 (String)envprops.get(Context.PROVIDER_URL) : null;
  66 
  67             // If URL not in environment, use defaults
  68             if (providerUrl == null) {
  69                 return new LdapCtx("", LdapCtx.DEFAULT_HOST,
  70                     LdapCtx.DEFAULT_PORT, envprops, false);
  71             }
  72 
  73             // Extract URL(s)
  74             String[] urls = LdapURL.fromList(providerUrl);
  75 
  76             if (urls.length == 0) {
  77                 throw new ConfigurationException(Context.PROVIDER_URL +
  78                     " property does not contain a URL");
  79             }
  80 
  81             // Generate an LDAP context
  82             return getLdapCtxInstance(urls, envprops);
  83 
  84         } catch (LdapReferralException e) {
  85 
  86             if (envprops != null &&
  87                 "throw".equals(envprops.get(Context.REFERRAL))) {
  88                 throw e;
  89             }
  90 
  91             Control[] bindCtls = (envprops != null)?
  92                 (Control[])envprops.get(LdapCtx.BIND_CONTROLS) : null;
  93 
  94             return (LdapCtx)e.getReferralContext(envprops, bindCtls);
  95         }
  96     }
  97 
  98     /**
  99      * Returns true if argument is an LDAP reference.
 100      */
 101     private static boolean isLdapRef(Object obj) {
 102 
 103         if (!(obj instanceof Reference)) {
 104             return false;
 105         }
 106         String thisClassName = LdapCtxFactory.class.getName();
 107         Reference ref = (Reference)obj;
 108 
 109         return thisClassName.equals(ref.getFactoryClassName());
 110     }
 111 
 112     /**
 113      * Returns the URLs contained within an LDAP reference.
 114      */
 115     private static String[] getURLs(Reference ref) throws NamingException {
 116 
 117         int size = 0;   // number of URLs
 118         String[] urls = new String[ref.size()];
 119 
 120         Enumeration<RefAddr> addrs = ref.getAll();
 121         while (addrs.hasMoreElements()) {
 122             RefAddr addr = addrs.nextElement();
 123 
 124             if ((addr instanceof StringRefAddr) &&
 125                 addr.getType().equals(ADDRESS_TYPE)) {
 126 
 127                 urls[size++] = (String)addr.getContent();
 128             }
 129         }
 130         if (size == 0) {
 131             throw (new ConfigurationException(
 132                     "Reference contains no valid addresses"));
 133         }
 134 
 135         // Trim URL array down to size.
 136         if (size == ref.size()) {
 137             return urls;
 138         }
 139         String[] urls2 = new String[size];
 140         System.arraycopy(urls, 0, urls2, 0, size);
 141         return urls2;
 142     }
 143 
 144     // ------------ Utilities used by other classes ----------------
 145 
 146     public static DirContext getLdapCtxInstance(Object urlInfo, Hashtable<?,?> env)
 147             throws NamingException {
 148 
 149         if (urlInfo instanceof String) {
 150             return getUsingURL((String)urlInfo, env);
 151         } else if (urlInfo instanceof String[]) {
 152             return getUsingURLs((String[])urlInfo, env);
 153         } else {
 154             throw new IllegalArgumentException(
 155                 "argument must be an LDAP URL String or array of them");
 156         }
 157     }
 158 
 159     private static DirContext getUsingURL(String url, Hashtable<?,?> env)
 160             throws NamingException
 161     {
 162         try {
 163             LdapDnsProviderResult r =
 164                 LdapDnsProviderService.getInstance().lookupEndpoints(url, env);
 165             LdapCtx ctx = null;
 166             NamingException lastException = null;
 167 
 168             /*
 169              * Prior to this change we had been assuming that the url.getDN()
 170              * should be converted to a domain name via
 171              * ServiceLocator.mapDnToDomainName(url.getDN())
 172              *
 173              * However this is incorrect as we can't assume that the supplied
 174              * url.getDN() is the same as the dns domain for the directory
 175              * server.
 176              *
 177              * This means that we depend on the dnsProvider to return both
 178              * the list of urls of individual hosts from which we attempt to
 179              * create an LdapCtx from *AND* the domain name that they serve
 180              *
 181              * In order to do this the dnsProvider must return an
 182              * {@link LdapDnsProviderResult}.
 183              *
 184              */
 185             for (String u : r.getEndpoints()) {
 186                 try {
 187                     ctx = getLdapCtxFromUrl(
 188                             r.getDomainName(), new LdapURL(u), env);
 189                 } catch (NamingException e) {
 190                     // try the next element
 191                     lastException = e;
 192                 }
 193             }
 194 
 195             if (lastException != null) {
 196                 throw lastException;
 197             }
 198 
 199             if (ctx == null) {
 200                 // we have resolved them, but they are not valid
 201                 throw new NamingException("Could not resolve a valid ldap host");
 202             }
 203 
 204             // Record the URL that created the context
 205             ctx.setProviderUrl(url);
 206             return ctx;
 207         } catch (NamingException e) {
 208             // getDnsUrls(url, env) may throw a NamingException, which there is
 209             // no need to wrap.
 210             throw e;
 211         } catch (Exception e) {
 212             NamingException ex = new NamingException();
 213             ex.setRootCause(e);
 214             throw ex;
 215         }
 216     }
 217 
 218     private static LdapCtx getLdapCtxFromUrl(String domain,
 219                                              LdapURL url,
 220                                              Hashtable<?,?> env)
 221             throws NamingException
 222     {
 223         String dn = url.getDN();
 224         String host = url.getHost();
 225         int port = url.getPort();
 226         LdapCtx ctx = new LdapCtx(dn, host, port, env, url.useSsl());
 227         ctx.setDomainName(domain);
 228         return ctx;
 229     }
 230 
 231     /*
 232      * Try each URL until one of them succeeds.
 233      * If all URLs fail, throw one of the exceptions arbitrarily.
 234      * Not pretty, but potentially more informative than returning null.
 235      */
 236     private static DirContext getUsingURLs(String[] urls, Hashtable<?,?> env)
 237             throws NamingException
 238     {
 239         NamingException ex = null;
 240         for (String u : urls) {
 241             try {
 242                 return getUsingURL(u, env);
 243             } catch (NamingException e) {
 244                 ex = e;
 245             }
 246         }
 247         throw ex;
 248     }
 249 
 250     /**
 251      * Used by Obj and obj/RemoteToAttrs too so must be public
 252      */
 253     public static Attribute createTypeNameAttr(Class<?> cl) {
 254         Vector<String> v = new Vector<>(10);
 255         String[] types = getTypeNames(cl, v);
 256         if (types.length > 0) {
 257             BasicAttribute tAttr =
 258                 new BasicAttribute(Obj.JAVA_ATTRIBUTES[Obj.TYPENAME]);
 259             for (int i = 0; i < types.length; i++) {
 260                 tAttr.add(types[i]);
 261             }
 262             return tAttr;
 263         }
 264         return null;
 265     }
 266 
 267     private static String[] getTypeNames(Class<?> currentClass, Vector<String> v) {
 268 
 269         getClassesAux(currentClass, v);
 270         Class<?>[] members = currentClass.getInterfaces();
 271         for (int i = 0; i < members.length; i++) {
 272             getClassesAux(members[i], v);
 273         }
 274         String[] ret = new String[v.size()];
 275         int i = 0;
 276 
 277         for (String name : v) {
 278             ret[i++] = name;
 279         }
 280         return ret;
 281     }
 282 
 283     private static void getClassesAux(Class<?> currentClass, Vector<String> v) {
 284         if (!v.contains(currentClass.getName())) {
 285             v.addElement(currentClass.getName());
 286         }
 287         currentClass = currentClass.getSuperclass();
 288 
 289         while (currentClass != null) {
 290             getTypeNames(currentClass, v);
 291             currentClass = currentClass.getSuperclass();
 292         }
 293     }
 294 }