1 /*
   2  * Copyright (c) 1999, 2006, 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.Hashtable;
  29 import java.util.Vector;
  30 import java.util.Enumeration;
  31 import java.net.MalformedURLException;
  32 
  33 import javax.naming.*;
  34 import javax.naming.directory.*;
  35 import javax.naming.spi.ObjectFactory;
  36 import javax.naming.spi.InitialContextFactory;
  37 import javax.naming.ldap.Control;
  38 
  39 import com.sun.jndi.url.ldap.ldapURLContextFactory;
  40 
  41 final public class LdapCtxFactory implements ObjectFactory, InitialContextFactory {
  42     /**
  43      * The type of each address in an LDAP reference.
  44      */
  45     public final static String ADDRESS_TYPE = "URL";
  46 
  47     // ----------------- ObjectFactory interface --------------------
  48 
  49     public Object getObjectInstance(Object ref, Name name, Context nameCtx,
  50         Hashtable<?,?> env) throws Exception {
  51 
  52         if (!isLdapRef(ref)) {
  53             return null;
  54         }
  55         ObjectFactory factory = new ldapURLContextFactory();
  56         String[] urls = getURLs((Reference)ref);
  57         return factory.getObjectInstance(urls, name, nameCtx, env);
  58     }
  59 
  60     // ----------------- InitialContext interface  --------------------
  61 
  62     public Context getInitialContext(Hashtable<?,?> envprops)
  63         throws NamingException {
  64 
  65         try {
  66             String providerUrl = (envprops != null) ?
  67                 (String)envprops.get(Context.PROVIDER_URL) : null;
  68 
  69             // If URL not in environment, use defaults
  70             if (providerUrl == null) {
  71                 return new LdapCtx("", LdapCtx.DEFAULT_HOST,
  72                     LdapCtx.DEFAULT_PORT, envprops, false);
  73             }
  74 
  75             // Extract URL(s)
  76             String[] urls = LdapURL.fromList(providerUrl);
  77 
  78             if (urls.length == 0) {
  79                 throw new ConfigurationException(Context.PROVIDER_URL +
  80                     " property does not contain a URL");
  81             }
  82 
  83             // Generate an LDAP context
  84             return getLdapCtxInstance(urls, envprops);
  85 
  86         } catch (LdapReferralException e) {
  87 
  88             if (envprops != null &&
  89                 "throw".equals(envprops.get(Context.REFERRAL))) {
  90                 throw e;
  91             }
  92 
  93             Control[] bindCtls = (envprops != null)?
  94                 (Control[])envprops.get(LdapCtx.BIND_CONTROLS) : null;
  95 
  96             return (LdapCtx)e.getReferralContext(envprops, bindCtls);
  97         }
  98     }
  99 
 100     /**
 101      * Returns true if argument is an LDAP reference.
 102      */
 103     private static boolean isLdapRef(Object obj) {
 104 
 105         if (!(obj instanceof Reference)) {
 106             return false;
 107         }
 108         String thisClassName = LdapCtxFactory.class.getName();
 109         Reference ref = (Reference)obj;
 110 
 111         return thisClassName.equals(ref.getFactoryClassName());
 112     }
 113 
 114     /**
 115      * Returns the URLs contained within an LDAP reference.
 116      */
 117     private static String[] getURLs(Reference ref) throws NamingException {
 118 
 119         int size = 0;   // number of URLs
 120         String[] urls = new String[ref.size()];
 121 
 122         Enumeration addrs = ref.getAll();
 123         while (addrs.hasMoreElements()) {
 124             RefAddr addr = (RefAddr)addrs.nextElement();
 125 
 126             if ((addr instanceof StringRefAddr) &&
 127                 addr.getType().equals(ADDRESS_TYPE)) {
 128 
 129                 urls[size++] = (String)addr.getContent();
 130             }
 131         }
 132         if (size == 0) {
 133             throw (new ConfigurationException(
 134                     "Reference contains no valid addresses"));
 135         }
 136 
 137         // Trim URL array down to size.
 138         if (size == ref.size()) {
 139             return urls;
 140         }
 141         String[] urls2 = new String[size];
 142         System.arraycopy(urls, 0, urls2, 0, size);
 143         return urls2;
 144     }
 145 
 146     // ------------ Utilities used by other classes ----------------
 147 
 148     public static DirContext getLdapCtxInstance(Object urlInfo, Hashtable env)
 149             throws NamingException {
 150 
 151         if (urlInfo instanceof String) {
 152             return getUsingURL((String)urlInfo, env);
 153         } else if (urlInfo instanceof String[]) {
 154             return getUsingURLs((String[])urlInfo, env);
 155         } else {
 156             throw new IllegalArgumentException(
 157                 "argument must be an LDAP URL String or array of them");
 158         }
 159     }
 160 
 161     private static DirContext getUsingURL(String url, Hashtable env)
 162             throws NamingException {
 163         DirContext ctx = null;
 164         LdapURL ldapUrl = new LdapURL(url);
 165         String dn = ldapUrl.getDN();
 166         String host = ldapUrl.getHost();
 167         int port = ldapUrl.getPort();
 168         String[] hostports;
 169         String domainName = null;
 170 
 171         // handle a URL with no hostport (ldap:/// or ldaps:///)
 172         // locate the LDAP service using the URL's distinguished name
 173         if (host == null &&
 174             port == -1 &&
 175             dn != null &&
 176             (domainName = ServiceLocator.mapDnToDomainName(dn)) != null &&
 177             (hostports = ServiceLocator.getLdapService(domainName, env))
 178                 != null) {
 179             // Generate new URLs that include the discovered hostports.
 180             // Reuse the original URL scheme.
 181             String scheme = ldapUrl.getScheme() + "://";
 182             String[] newUrls = new String[hostports.length];
 183             String query = ldapUrl.getQuery();
 184             String urlSuffix = ldapUrl.getPath() + (query != null ? query : "");
 185             for (int i = 0; i < hostports.length; i++) {
 186                 newUrls[i] = scheme + hostports[i] + urlSuffix;
 187             }
 188             ctx = getUsingURLs(newUrls, env);
 189             // Associate the derived domain name with the context
 190             ((LdapCtx)ctx).setDomainName(domainName);
 191 
 192         } else {
 193             ctx = new LdapCtx(dn, host, port, env, ldapUrl.useSsl());
 194             // Record the URL that created the context
 195             ((LdapCtx)ctx).setProviderUrl(url);
 196         }
 197         return ctx;
 198     }
 199 
 200     /*
 201      * Try each URL until one of them succeeds.
 202      * If all URLs fail, throw one of the exceptions arbitrarily.
 203      * Not pretty, but potentially more informative than returning null.
 204      */
 205     private static DirContext getUsingURLs(String[] urls, Hashtable env)
 206             throws NamingException {
 207         NamingException ne = null;
 208         DirContext ctx = null;
 209         for (int i = 0; i < urls.length; i++) {
 210             try {
 211                 return getUsingURL(urls[i], env);
 212             } catch (AuthenticationException e) {
 213                 throw e;
 214             } catch (NamingException e) {
 215                 ne = e;
 216             }
 217         }
 218         throw ne;
 219     }
 220 
 221     /**
 222      * Used by Obj and obj/RemoteToAttrs too so must be public
 223      */
 224     public static Attribute createTypeNameAttr(Class cl) {
 225         Vector v = new Vector(10);
 226         String[] types = getTypeNames(cl, v);
 227         if (types.length > 0) {
 228             BasicAttribute tAttr =
 229                 new BasicAttribute(Obj.JAVA_ATTRIBUTES[Obj.TYPENAME]);
 230             for (int i = 0; i < types.length; i++) {
 231                 tAttr.add(types[i]);
 232             }
 233             return tAttr;
 234         }
 235         return null;
 236     }
 237 
 238     private static String[] getTypeNames(Class currentClass, Vector v) {
 239 
 240         getClassesAux(currentClass, v);
 241         Class[] members = currentClass.getInterfaces();
 242         for (int i = 0; i < members.length; i++) {
 243             getClassesAux(members[i], v);
 244         }
 245         String[] ret = new String[v.size()];
 246         int i = 0;
 247         for (java.util.Enumeration e = v.elements(); e.hasMoreElements();) {
 248             ret[i++] = (String)e.nextElement();
 249         }
 250         return ret;
 251     }
 252 
 253     private static void getClassesAux(Class currentClass, Vector v) {
 254         if (!v.contains(currentClass.getName())) {
 255             v.addElement(currentClass.getName());
 256         }
 257         currentClass = currentClass.getSuperclass();
 258 
 259         while (currentClass != null) {
 260             getTypeNames(currentClass, v);
 261             currentClass = currentClass.getSuperclass();
 262         }
 263     }
 264 }