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