1 /*
   2  * Copyright (c) 2000, 2010, 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.dns;
  27 
  28 
  29 import java.net.MalformedURLException;
  30 import java.util.ArrayList;
  31 import java.util.Hashtable;
  32 import java.util.List;
  33 
  34 import javax.naming.*;
  35 import javax.naming.spi.*;
  36 
  37 import com.sun.jndi.toolkit.url.UrlUtil;
  38 import 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 
  54 public 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         StringBuffer buf = new StringBuffer();
 202         for (int i = 0; i < servers.length; i++) {
 203             if (i > 0) {
 204                 buf.append(' ');
 205             }
 206             buf.append("dns://").append(servers[i]).append(path);
 207         }
 208         return buf.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 filterNameServers(List input, boolean oneIsEnough) {
 227         SecurityManager security = System.getSecurityManager();
 228         if (security == null || input == null || input.isEmpty()) {
 229             return input;
 230         } else {
 231             List output = new ArrayList();
 232             for (Object o: input) {
 233                 if (o instanceof String) {
 234                     String platformServer = (String)o;
 235                     int colon = platformServer.indexOf(':',
 236                             platformServer.indexOf(']') + 1);
 237 
 238                     int p = (colon < 0)
 239                         ? DEFAULT_PORT
 240                         : Integer.parseInt(
 241                             platformServer.substring(colon + 1));
 242                     String s = (colon < 0)
 243                         ? platformServer
 244                         : platformServer.substring(0, colon);
 245                     try {
 246                         security.checkConnect(s, p);
 247                         output.add(platformServer);
 248                         if (oneIsEnough) {
 249                             return output;
 250                         }
 251                     } catch (SecurityException se) {
 252                         continue;
 253                     }
 254                 }
 255             }
 256             return output;
 257         }
 258     }
 259 }