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 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 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 (String[]) servers.toArray(
 164                                         new String[servers.size()]);
 165     }
 166 
 167     /*
 168      * Returns true if serversForUrls(urls) would make use of servers
 169      * from the underlying platform.
 170      */
 171     private static boolean platformServersUsed(DnsUrl[] urls) {
 172         if (!platformServersAvailable()) {
 173             return false;
 174         }
 175         for (int i = 0; i < urls.length; i++) {
 176             if (urls[i].getHost() == null &&
 177                 urls[i].getPort() < 0) {
 178                 return true;
 179             }
 180         }
 181         return false;
 182     }
 183 
 184     /*
 185      * Returns a value for the PROVIDER_URL property (space-separated URL
 186      * Strings) that reflects the given domain and servers.
 187      * Each server is of the form "server[:port]".
 188      * There must be at least one server.
 189      * IPv6 literal host names include delimiting brackets.
 190      */
 191     private static String constructProviderUrl(String domain,
 192                                                String[] servers) {
 193         String path = "";
 194         if (!domain.equals(".")) {
 195             try {
 196                 path = "/" + UrlUtil.encode(domain, "ISO-8859-1");
 197             } catch (java.io.UnsupportedEncodingException e) {
 198                 // assert false : "ISO-Latin-1 charset unavailable";
 199             }
 200         }
 201 
 202         StringBuffer buf = new StringBuffer();
 203         for (int i = 0; i < servers.length; i++) {
 204             if (i > 0) {
 205                 buf.append(' ');
 206             }
 207             buf.append("dns://").append(servers[i]).append(path);
 208         }
 209         return buf.toString();
 210     }
 211 
 212     /*
 213      * Reads environment to find URL(s) of initial context.
 214      * Default URL is "dns:".
 215      */
 216     private static String getInitCtxUrl(Hashtable env) {
 217         String url = (String) env.get(Context.PROVIDER_URL);
 218         return ((url != null) ? url : DEFAULT_URL);
 219     }
 220 
 221     /**
 222      * Removes any DNS server that's not permitted to access
 223      * @param input the input server[:port] list, must not be null
 224      * @param oneIsEnough return output once there exists one ok
 225      * @return the filtered list, all non-permitted input removed
 226      */
 227     private static List filterNameServers(List input, boolean oneIsEnough) {
 228         SecurityManager security = System.getSecurityManager();
 229         if (security == null || input == null || input.isEmpty()) {
 230             return input;
 231         } else {
 232             List output = new ArrayList();
 233             for (Object o: input) {
 234                 if (o instanceof String) {
 235                     String platformServer = (String)o;
 236                     int colon = platformServer.indexOf(':',
 237                             platformServer.indexOf(']') + 1);
 238 
 239                     int p = (colon < 0)
 240                         ? DEFAULT_PORT
 241                         : Integer.parseInt(
 242                             platformServer.substring(colon + 1));
 243                     String s = (colon < 0)
 244                         ? platformServer
 245                         : platformServer.substring(0, colon);
 246                     try {
 247                         security.checkConnect(s, p);
 248                         output.add(platformServer);
 249                         if (oneIsEnough) {
 250                             return output;
 251                         }
 252                     } catch (SecurityException se) {
 253                         continue;
 254                     }
 255                 }
 256             }
 257             return output;
 258         }
 259     }
 260 }