1/*
2 * Copyright (c) 2000, 2011, 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.dns;
27
28
29import java.net.MalformedURLException;
30import java.util.ArrayList;
31import java.util.Hashtable;
32import java.util.List;
33
34import javax.naming.*;
35import javax.naming.spi.*;
36
37import com.sun.jndi.toolkit.url.UrlUtil;
38import sun.net.dns.ResolverConfiguration;       // available since 1.4.1
39
40
41/**
42 * A DnsContextFactory serves as the initial context factory for DNS.
43 *
44 * <p> When an initial context is being created, the environment
45 * property "java.naming.provider.url" should contain a DNS pseudo-URL
46 * (see DnsUrl) or a space-separated list of them.  Multiple URLs must
47 * all have the same domain value.
48 * If the property is not set, the default "dns:" is used.
49 *
50 * @author Scott Seligman
51 */
52
53
54public class DnsContextFactory implements InitialContextFactory {
55
56    private static final String DEFAULT_URL = "dns:";
57    private static final int DEFAULT_PORT = 53;
58
59
60    public Context getInitialContext(Hashtable<?,?> env) throws NamingException {
61        if (env == null) {
62            env = new Hashtable<>(5);
63        }
64        return urlToContext(getInitCtxUrl(env), env);
65    }
66
67    public static DnsContext getContext(String domain,
68                                        String[] servers, Hashtable<?,?> env)
69            throws NamingException {
70        return new DnsContext(domain, servers, env);
71    }
72
73    /*
74     * "urls" are used to determine the servers, but any domain
75     * components are overridden by "domain".
76     */
77    public static DnsContext getContext(String domain,
78                                        DnsUrl[] urls, Hashtable<?,?> env)
79            throws NamingException {
80
81        String[] servers = serversForUrls(urls);
82        DnsContext ctx = getContext(domain, servers, env);
83        if (platformServersUsed(urls)) {
84            ctx.setProviderUrl(constructProviderUrl(domain, servers));
85        }
86        return ctx;
87    }
88
89    /*
90     * Public for use by product test suite.
91     */
92    public static boolean platformServersAvailable() {
93        return !filterNameServers(
94                    ResolverConfiguration.open().nameservers(), true
95                ).isEmpty();
96    }
97
98    private static Context urlToContext(String url, Hashtable<?,?> env)
99            throws NamingException {
100
101        DnsUrl[] urls;
102        try {
103            urls = DnsUrl.fromList(url);
104        } catch (MalformedURLException e) {
105            throw new ConfigurationException(e.getMessage());
106        }
107        if (urls.length == 0) {
108            throw new ConfigurationException(
109                    "Invalid DNS pseudo-URL(s): " + url);
110        }
111        String domain = urls[0].getDomain();
112
113        // If multiple urls, all must have the same domain.
114        for (int i = 1; i < urls.length; i++) {
115            if (!domain.equalsIgnoreCase(urls[i].getDomain())) {
116                throw new ConfigurationException(
117                        "Conflicting domains: " + url);
118            }
119        }
120        return getContext(domain, urls, env);
121    }
122
123    /*
124     * Returns all the servers specified in a set of URLs.
125     * If a URL has no host (or port), the servers configured on the
126     * underlying platform are used if possible.  If no configured
127     * servers can be found, then fall back to the old behavior of
128     * using "localhost".
129     * There must be at least one URL.
130     */
131    private static String[] serversForUrls(DnsUrl[] urls)
132            throws NamingException {
133
134        if (urls.length == 0) {
135            throw new ConfigurationException("DNS pseudo-URL required");
136        }
137
138        List<String> servers = new ArrayList<>();
139
140        for (int i = 0; i < urls.length; i++) {
141            String server = urls[i].getHost();
142            int port = urls[i].getPort();
143
144            if (server == null && port < 0) {
145                // No server or port given, so look to underlying platform.
146                // ResolverConfiguration does some limited caching, so the
147                // following is reasonably efficient even if called rapid-fire.
148                List<String> platformServers = filterNameServers(
149                    ResolverConfiguration.open().nameservers(), false);
150                if (!platformServers.isEmpty()) {
151                    servers.addAll(platformServers);
152                    continue;  // on to next URL (if any, which is unlikely)
153                }
154            }
155
156            if (server == null) {
157                server = "localhost";
158            }
159            servers.add((port < 0)
160                        ? server
161                        : server + ":" + port);
162        }
163        return servers.toArray(new String[servers.size()]);
164    }
165
166    /*
167     * Returns true if serversForUrls(urls) would make use of servers
168     * from the underlying platform.
169     */
170    private static boolean platformServersUsed(DnsUrl[] urls) {
171        if (!platformServersAvailable()) {
172            return false;
173        }
174        for (int i = 0; i < urls.length; i++) {
175            if (urls[i].getHost() == null &&
176                urls[i].getPort() < 0) {
177                return true;
178            }
179        }
180        return false;
181    }
182
183    /*
184     * Returns a value for the PROVIDER_URL property (space-separated URL
185     * Strings) that reflects the given domain and servers.
186     * Each server is of the form "server[:port]".
187     * There must be at least one server.
188     * IPv6 literal host names include delimiting brackets.
189     */
190    private static String constructProviderUrl(String domain,
191                                               String[] servers) {
192        String path = "";
193        if (!domain.equals(".")) {
194            try {
195                path = "/" + UrlUtil.encode(domain, "ISO-8859-1");
196            } catch (java.io.UnsupportedEncodingException e) {
197                // assert false : "ISO-Latin-1 charset unavailable";
198            }
199        }
200
201        StringBuilder sb = new StringBuilder();
202        for (int i = 0; i < servers.length; i++) {
203            if (i > 0) {
204                sb.append(' ');
205            }
206            sb.append("dns://").append(servers[i]).append(path);
207        }
208        return sb.toString();
209    }
210
211    /*
212     * Reads environment to find URL(s) of initial context.
213     * Default URL is "dns:".
214     */
215    private static String getInitCtxUrl(Hashtable<?,?> env) {
216        String url = (String) env.get(Context.PROVIDER_URL);
217        return ((url != null) ? url : DEFAULT_URL);
218    }
219
220    /**
221     * Removes any DNS server that's not permitted to access
222     * @param input the input server[:port] list, must not be null
223     * @param oneIsEnough return output once there exists one ok
224     * @return the filtered list, all non-permitted input removed
225     */
226    private static List<String> filterNameServers(List<String> input, boolean oneIsEnough) {
227        SecurityManager security = System.getSecurityManager();
228        if (security == null || input == null || input.isEmpty()) {
229            return input;
230        } else {
231            List<String> output = new ArrayList<>();
232            for (String platformServer: input) {
233                int colon = platformServer.indexOf(':',
234                        platformServer.indexOf(']') + 1);
235
236                int p = (colon < 0)
237                    ? DEFAULT_PORT
238                    : Integer.parseInt(
239                        platformServer.substring(colon + 1));
240                String s = (colon < 0)
241                    ? platformServer
242                    : platformServer.substring(0, colon);
243                try {
244                    security.checkConnect(s, p);
245                    output.add(platformServer);
246                    if (oneIsEnough) {
247                        return output;
248                    }
249                } catch (SecurityException se) {
250                    continue;
251                }
252            }
253            return output;
254        }
255    }
256}
257